Google ranks URLs, not intentions: the NN- prefix strip that resolved eight cannibalisation pairs on ukcalculator.uk

The /site-audit-on-page-seo skill was halfway through a site-wide-technical pass against localhost:3011 when the first cannibalisation pair surfaced: two URLs, one search intent, signals split between them. The prefix on the first slug was 01-. The next seven pairs had the same pattern.

That was the audit result that locked Phase 1.3’s scope.

What a sort key does to a URL graph

The NN- pattern was a directory convention. Slugs like 01-percentage-calculator and 02-bmi-calculator kept the content folder readable and build ordering deterministic. No complaints about either of those things.

The problem is that Google doesn’t know a slug prefix is organisational. It sees a URL. It crawls it. If internal MDX links point to a mix of the prefixed form and the bare slug, both versions accumulate some crawl attention. That mix happens naturally as content accumulates across different writing sessions. Both look like candidates. Neither gets the full consolidation of ranking signals that the canonical URL should have.

Eight pairs meant sixteen URLs in an ambiguous state. og:title is irrelevant to this problem. Canonical hints help, but only if the URL graph isn’t actively reinforcing the old form. Meta-tags communicate intent to a system that has already decided which URL to rank. Until Google has consolidated, those instructions are partially addressed.

Phase 1.3: the atomic operation

The fix was deliberately atomic: one PR, three groups of file changes, a full verification loop, zero soft-failures.

Group one: universal NN- prefix strip. Every affected slug dropped its numeric prefix. The bare slug becomes the canonical. This is the largest surface-area change. It propagates through the route map, the content directory, and all generated links.

Group two: 23 × 308 redirects. Each old prefixed URL redirects permanently to the canonical. 308 rather than 301: the method-preservation semantics matter in an SSR context where redirects can propagate through server-side fetch chains. The redirect entries are the signal to Google: “consolidate everything you attributed to /01-percentage-calculator into /percentage-calculator.”

Group three: internal MDX cross-links repointed at canonical URLs. Redirects catch external and search-engine traffic. They don’t fix the internal link graph. If MDX articles are still linking to the old URL, the crawler sees a redirect on every internal crawl. That keeps the old URL alive in its model of the site. Every affected cross-link was updated to the canonical before the PR merged. The verification loop confirmed zero stale links.

The three groups had to land together. A prefix strip without redirects produces 404s. Redirects without internal-link repointing keeps the old URL in regular circulation. Repointing without redirects breaks external links. None of the three is complete without the others.

PRs #99 through #102 merged on 2026-06-14 at 21:24 BST.

What the rest of Phase 1 covered

The cannibalisation fix was the structural layer. The rest of Phase 1 operated on signals: a different layer, genuinely necessary, but downstream of URL health.

Phase 1.1: title double-branding fixed. Some layouts were appending the site name twice in the <title> element. Not a human-noticeable problem; a crawler-legibility problem.

Phase 1.2: dynamic og:image and twitter:image on every route via next/og ImageResponse. Two Aether-themed templates with per-article variants. The Tier-1 static fallback was skipped. next/og works on this stack; no separate image-generation step needed.

Phase 1.5: public/robots.txt migrated to app/robots.ts as a MetadataRoute.Robots export. Same crawl policy as before, but now type-safe and part of the Next.js build. The GSC indexing action plan was filed alongside this phase. No point requesting aggressive indexation before the structural problems were resolved.

Why Phase 1.3 was the most consequential change

Phase 1.2 produced visible output. OG images show up immediately in link previews. The robots.ts migration is sound engineering. All of it compounds.

But none of it moves the needle on a site where the URL graph is giving Google competing candidates for the same search intent. You cannot og:title your way out of a cannibalisation problem. Meta-tags are instructions to a system that has to have decided which URL is canonical before it acts on those instructions. Phase 1.3 is what makes that decision possible.

The redirect pattern is straightforward. The internal-link sweep is the discipline that matters. It’s the part where you have to grep every piece of content for every old slug pattern and update it before declaring the operation done. Phase 1.3’s verification loop enforced this. Zero soft-failures is the correct bar for a change this structural.

Three days later

Three days after the Phase 1 stack merged, Google started indexing pages on ukcalculator.uk. That’s a qualitative signal, not a ranking result. It means the technical foundation is legible: canonical signals consistent, crawl policy not blocking anything it shouldn’t, pages worth indexing.

Whether the cannibalisation resolution translates to measurable ranking improvement is a question for the coming weeks. The indexation starting is a prerequisite, not the result itself.

The practical takeaway

If you’re running a Next.js content site with slug-based routing and you’ve ever used a numeric sort prefix, audit for cannibalisation before you touch metadata. The pairs surface clearly in a site-wide SEO pass or in Search Console when you look for multiple URLs competing on the same queries.

The 308 redirect pattern is a redirects() export in next.config.js, one entry per old slug. That part is mechanical. The discipline is the content sweep: grep every MDX file for the old slug patterns and update every match. Stale internal links keep the old URL in circulation. The verification step is not optional.

Phase 1.3 was the least visible PR in the set: slug renames, redirect entries, an MDX cross-link sweep. Phase 1.2’s OG images landed immediately in previews. But the ranking impact was in Phase 1.3, not Phase 1.2. Fix the URL graph first. Signal work builds on top of it; it doesn’t substitute for it.

All writing