SEO Audit Findings: The Errors That Keep Coming Back on My 14 jekcms Sites

Repeated audits of my own 14-site jekcms fleet keep finding the same things: empty legal pages, soft-404s, broken hreflang, spammy auto-links — and the fixes.

SEO Audit Findings: The Errors That Keep Coming Back on My 14 jekcms Sites

Repeated audits of my own 14-site jekcms fleet keep finding the same things: empty legal pages, soft-404s, broken hreflang, spammy auto-links — and the fixes.

I don't audit client sites. I audit my own — fourteen content sites, all running on jekcms, somewhere north of 5,500 posts between them. The biggest one alone carries over 2,300. And because I keep running the same checks against the same fleet, I've stopped being surprised by what turns up. It's almost never exotic. It's the same handful of problems wearing slightly different clothes each time.

A confession before the list: an earlier version of this post opened with a big aggregate statistic about audited sites and error counts. I've deleted it. I can no longer verify those numbers, and a post about finding problems shouldn't lead with one it can't back up. Everything below happened on my own installs. For each finding: the symptom, how I actually detected it, and what fixed it.

Symptom: privacy and terms pages rendering as blank white pages on four of the fourteen sites. Nobody noticed for a while — who reads their own privacy policy? I found it while preparing an AdSense application, which is the worst possible moment, because an empty legal page is a rejection-grade defect.

Detection: the theme's legal.php existed on disk and had a perfectly plausible size: 14,616 bytes. All 14,616 of them were NUL bytes. A file transfer had zeroed the content while preserving the length, so every "does the file exist" check passed and so did every size check. The one-liner that exposed it:

grep -rlP '\x00' --include='*.php' themes/

Fix: restore from git, then make the mistake impossible to repeat. CI now scans every push for NUL bytes and empty PHP stubs and fails the build on a hit. That guard was written the same day, in June 2026, and it has earned its keep since.

Soft-404: the 200 that should have been a 404

Symptom: Search Console flagging "Soft 404" on URLs that were genuinely gone. The pages returned an empty layout — header, footer, nothing in between — with a 200 status.

Detection: one command against a URL you know doesn't exist:

curl -sI https://example.com/blog/does-not-exist | head -1

If that prints HTTP/2 200, you have a soft-404.

Root cause: a classic PHP ordering bug. The 404 status was being set after header.php had already sent output, so the headers were long gone and PHP silently kept the 200. Fix: decide the status code before a single byte of output leaves the server, and put a noindex robots meta on the error page as a second line of defense. Be warned: Google's trust in the affected sections came back slowly. This one costs you months, not days.

One hreflang pair, declared by 87 pages

Symptom: Turkish documentation pages underperforming for Turkish queries despite existing, being indexed, and being perfectly fine as content.

Detection: I curled a deep docs page and actually read the head. Every one of the 87 docs pages declared the exact same hreflang pair — /docs and /tr/dokumantasyon — no matter which page you were on. Not a single page declared itself. Google requires a hreflang set to be reciprocal and to include a self-reference; declare somebody else's URLs and the whole cluster is quietly discarded.

Fix: the pair is now generated from the request path, so each page declares its own two language variants. The Turkish slugs are properly localized (/tr/hakkimizda, /tr/fiyatlandirma) rather than machine-mirrored English. The longer version of this saga is in the hreflang and canonical strategy post.

Database drift across roughly 2,700 posts

Symptom: the sneaky kind — no error page anywhere, just posts quietly misbehaving. Orphaned category references. Image paths missing their directory prefix, so a hero image 404s while the post renders fine. Fields that disagree with each other.

Detection: a set of SQL sweep queries run against every site's database. The pattern is always the same: find references that point at nothing. For example, posts attached to categories that no longer exist:

SELECT p.id, p.slug FROM posts p
LEFT JOIN categories c ON c.id = p.category_id
WHERE c.id IS NULL;

Fix: one scripted repair pass across all fourteen databases in June 2026 — about 2,700 posts touched in total. The real lesson wasn't the repair, though. It was that drift accumulates invisibly, so the sweep queries now live in a file and get re-run on a schedule instead of being reinvented every time something feels off.

137 posts wearing the wrong meta

Symptom: search snippets that didn't match the page — meta descriptions that belonged to other posts, or to nothing at all. A visitor clicks expecting one thing and lands on another. That mismatch shows up in behavior, and behavior shows up in rankings.

Detection: found during the same fleet sweep. Compare the stored meta description against the post it's attached to; a description referencing topics the post never mentions is the giveaway.

Fix: 137 bad metas cleared in one pass. Counterintuitive but true: an empty meta description beats a wrong one. Google will generate a snippet from the actual content, and its guess is better than a lie.

The auto-linker that made my sites look like catalog spam

This one is a self-inflicted wound, so let me own it properly.

Symptom: pages where the same keyword turned into a link on every single occurrence, paragraph after paragraph. Individually each link looked defensible. Collectively the sites read like affiliate catalogs. I believe it contributed to an AdSense rejection — mechanical internal linking layered on top of template-flavored prose is exactly what a reviewer's "made for ads" instinct triggers on.

Detection: honestly? Eyes. Open five random posts and read them as a stranger would. A per-post link count query confirms the outliers, but you will see the problem before you measure it.

Fix: the slow, boring one. Post by post, links chosen editorially — kept where a reader would genuinely want to go somewhere, deleted everywhere else. There is no script for judgment. The auto-linker still exists in my toolbox, but it runs throttled and its output gets reviewed before anything ships.

Pagination that never says no

Symptom: ?page=500 on a blog with twelve real pages returned a 200 and an empty list. Crawlers found those URLs, kept requesting them, and burned crawl budget on infinite nothing.

Detection:

curl -so /dev/null -w '%{http_code}\n' 'https://example.com/blog?page=9999'

Fix: out-of-range page numbers now return a real 404 in jekcms. A small change with an immediate, visible effect on crawl stats.

Excerpts in the wrong language

Symptom: on the bilingual sites, Turkish listing pages showing English excerpts and vice versa. To a visitor it just looks sloppy. To Google it muddies the language signal on pages that are otherwise cleanly separated by path.

Detection: crude heuristics work fine here. English stopwords almost never occur naturally inside a Turkish excerpt field:

SELECT id, slug FROM posts
WHERE lang = 'tr'
AND (excerpt LIKE '% the %' OR excerpt LIKE '% and %');

Adapt the column names to your schema; the idea travels.

Fix: regenerate the excerpt from the correct-language body — and then find where the wrong text was coming from. Mine leaked in through an import path that defaulted to the wrong source field, so fixing the data without fixing the pipe would have lasted exactly one import.

What this list has in common

Not one of these is a content-quality problem. They're plumbing. And every single one was detectable with a curl command, a grep, or a short SQL query — no paid tooling involved. If I had to compress the whole routine into one paragraph: request URLs that shouldn't exist and check the status code, read the hreflang tags on a deep page rather than the homepage, scan your files for the impossible, and sweep the database for references that point at nothing.

The strategic ranking mistakes — as opposed to these mechanical ones — are a separate post: five CMS mistakes that kill your Google rankings. And if you'd rather have the checks wired into the platform than into your habits, the docs cover what jekcms now does automatically. Several of these fixes shipped as product changes for one simple reason: I got tired of finding the same thing twice.

Written by

Celil Uyanıkoğlu

Computer engineer with 25+ years in IT. He builds jekcms and runs his own network of content sites on it — every guide published here is tried on those live installs first.

See all posts →

Order Today

One-time payment, lifetime access. Setup in 30 minutes.

View Pricing
  • Setup and live in 30 minutes
  • 13 professional themes
  • AVIF/WebP image optimization
  • Automatic SEO — Sitemap, Schema.org
  • ZeroTrack cookieless analytics

Be the first to know

New features, release notes & CMS guides — a couple of emails a month, no spam.