Two weeks ago I shipped a fix to my travel site with a commit message that named the wrong cause.
The headline change was real work: 220 country pages had never been prerendered, so every crawler that asked for one got the raw single-page-app shell — identical bytes for all 220, canonical pointing at the homepage. Google had filed them as duplicates of each other and of the front page, which is exactly what they looked like. Fixing it meant pulling a 2,800-line data structure out of a React component (it carried icon references, so no build script could import it), and the component went from 4,343 lines to 1,417. Call it 4,600 lines changed across nine files.
While wiring that up I found a second bug, unrelated except by proximity, and fixed it in the same commit almost as an afterthought. It was a lookup: one function building a URL from the wrong source. Maybe fifteen lines.
The obvious way to check whether a fix worked is to look at the thing you fixed and see if it went up. It went up. I nearly stopped there.
The problem is that everything went up that week — the whole site was 35% ahead, and I hadn't shipped anything to most of it. So "it went up" was worth nothing on its own. I needed a control: some part of the site with no structural changes in the same window, to measure the tide against the boat.
The blog section was the control — untouched in those weeks. It ran 19% ahead. That's the tide.
Against that baseline, comparing the eleven days before the deploy to the nine days after: The 220 newly prerendered country pages: +41% The hub page that links to them: +171%
The hub also moved from an average position in the high teens into the low teens. And here's the detail that killed my original story: 142 of those 220 country pages were already earning impressions before the fix. Only eight pages showed up for the first time afterward.
So the pages I'd spent two days rescuing from duplicate hell were, mostly, not in duplicate hell. They were doing fine. The page that improved dramatically was the one I'd changed almost by accident.
The hub page renders lists of countries, each one a link. It built those links from the database: take the row's region, take the row's slug, join them into a path. Straightforward.
But the router doesn't resolve URLs against the database. It resolves them against a static data file. And ten countries disagreed between the two.
Some disagreed on spelling. The database said united-kingdom, the router knew uk. The database said saint-lucia, the router knew st-lucia.
Some disagreed on something more interesting: which continent a country is in. The database filed Georgia, Armenia and Azerbaijan under Asia. The static file has them in Europe. Both are defensible — that's a genuinely contested boundary, and the two sources had been populated by different people at different times, each making a reasonable call.
If those links had 404'd, I'd have found them in a week. Every link checker I run would have flagged them, and a 404 in a crawl report is unambiguous.
They didn't 404. The route pattern /world/[continent]/[country] matched fine — asia is a plausible continent string and georgia is a plausible country string. The page rendered. It just had no country to render, so it fell through to the generic world-map copy. Status 200. Real HTML. A , a canonical tag pointing at itself.
That is not a broken link. That is a duplicate factory: a URL that manufactures a near-empty page, declares itself canonical, and gets vouched for by an inbound link from an indexed hub page. Ten of them, all generating the same generic content, all pointing at themselves, all endorsed by the one page in that section with the most authority.
And nothing in my toolchain could see it. The link checker asks "does this return an error?" — no. The typechecker asks "is this a valid string?" — yes. The build asks "did anything throw?" — no. The sitemap didn't list these URLs, so a sitemap audit wouldn't surface them either. The only signal was in the crawler's opinion of the section as a whole, which is not a signal you can grep for.
I've written a lot recently about facts stored in multiple places drifting apart. This is the same failure with a different payload: the identifiers drifted, not the facts. And identifier drift is sneakier, because a wrong fact eventually reads wrong to a human, while a wrong slug produces a page that looks perfectly fine — just not the page anyone meant.
