A neutral default ships garbage
My page scorer gave an empty page a passing grade, because every missing signal quietly scored as average.
By Andrew Pyle
The first clue was a page that announced its own poor quality to every reader.
01The page graded itself
A hub that printed its own score
Average content quality: 37/100
That line was not in a report. It sat in the visible body of a page that was ready to publish, printed for any reader to see. The page was one of 122 pool-city hub pages waiting in the queue on one of my sites. Their median was about 25 visible words. Their bodies also linked out with an unfilled placeholder where a real path should have been.
I found them during a readiness check. I was about to switch a harm gate from report mode to enforce mode, so I replayed the real gate, without writing anything, over sets of pages I already knew were bad. The verdict I wrote down was blunt. On its own, the gate does not make content safe, and it misses thin, empty, and placeholder pages.
That mattered because the daily publisher runs this gate on every run. Whatever it passed was what went live.
02Points for nothing
How an empty page reached forty
The quality scorer adds up parts, and a page at or above 40 may publish (60 for the more sensitive pages). I worked out what a page with no body at all would get. SEO metadata could add up to 15. Field coverage could add up to 15. Freshness and the navigation signal had nothing to measure, so each fell back to a neutral default of 5 to 10. The total landed between 40 and 45. An empty page cleared the bar.
The other safety net had the same hole from a different side. The harm gate skips empty bodies on purpose, because an empty body produces an all-zero fingerprint, and that is not a duplicate signal. So an empty page passed both checks. Neither one had been built to notice it.
A neutral default sounds harmless. In a sum with a pass line, it is a vote to pass. When freshness had nothing to say, I let it say about average, and about average added up to ship it.
03Absence scores zero
A missing body fails outright
The rule now is short. If a page has zero visible words, fewer than 20, a leaked debug line, or a visible unfilled placeholder, the scorer returns a total of 0 and a list of the substance problems it found. Real bodies score exactly as they did before.
When the gate blocks, its message names the reason and quotes the offending text, so the 37/100 line now shows up in the refusal itself. A blocked run reports something like this:
Substance gate blocked 122 page(s) (debug_text=122, thin_body=63); e.g. [id] /restaurants/tulsa-ok/ - debug_text: '...Average content quality: 37/100'The debug line had its own source. The function that builds hub bodies printed an internal metric straight into the page. I removed that line, and with it a per-child database loop that existed only to feed it.
Then I measured the new rule against every draft, ready, and published page before trusting it, with no writes and no model calls. The question was narrow: which pages passed the old gate and fail the new one? The answer was 608. When I looked at them by hand, all 608 were hub pages. No page outside the hubs tripped the leak detector, and no page anywhere had a fully empty body. Only 63 of them fell under the 20-word floor, all from that first set of 122. The problem lived in the hub template, and it did not reach the rest of the site.
04What the tests knew
The test fixtures were thin too
The measurement also caught my own false positives before they shipped. Three published blog posts with unbalanced code fences, and one package page carrying a pasted BSD licence with {organization} in it, matched as placeholders. Those are real pages. So indented lines and lines that look like format strings now count as code, and the new tests include a list of those real shapes that must pass.
There are 33 new tests. The detail that told me the most was in four older test files. They had to be rewritten, because their fixtures used bodies of two or three words. Thin bodies had been normal enough that my own tests were built on them. I also broke the rule on purpose three ways. Disabling the early return failed 7 tests. Breaking the debug pattern failed 5. Raising the word floor to 200 failed 3, including the one that checks a short but real page still passes.
What stays live
Pages already published stay live, because the gate only stops republishing and nothing republishes published pages on a schedule. Other callers of the scorer now give thin bodies a 0, so the next regeneration marks them noindex, which is where thin pages belong. No existing row changed. The next daily run should show those 122 hubs blocked with the debug line named as the reason.
Two limits stay open
The stored word count can be inflated (one hub stored 41 words against 16 visible), and I left it alone because changing it would move scores on every site. And 20 words is a floor against stubs. It sets no target for depth. A page that clears it has only proved it is not empty.
Related
writing
A zero page crawl is not a baseline
I shipped a batch of SEO fixes, and a clean reconciliation pass told me none of them had landed.
writing
Stop indexing what you copied
My sitemaps looked healthy because they counted pages I had not really written.
writing
A billion indexed pages is a direction, not a page count
I keep an absurdly large number as a north star — but the number isn't a target to hit by publishing a billion things. It's a forcing function that makes you solve the problems that only show up at scale, and it fails the moment you treat page count as the point.
writing
A sitemap should only list pages you actually want found
A sitemap is a set of recommendations you make to a search engine. Listing pages you've told it not to index, or that redirect elsewhere, is contradicting yourself — and a sitemap that contradicts itself teaches the crawler to trust you less.
writing
AEO and GEO: optimizing for answer engines
Two acronyms showed up in every SEO conversation this year. Here is the plain, first-hand version of what they mean and what actually earns them.