Your redirect list is about to swallow a page that still works
We deleted a large part of this site and mapped 322 dead URLs onto what replaced them. The interesting part was not writing the redirects. It was stopping one of them from permanently redirecting a page that still worked.
6 September 2026
Check it yourself. curl -sI https://boffincoders.com/website-development/projects
Most writing about redirects stops at "use a 301". That is the easy part, and it is not where sites get hurt. Sites get hurt in the second week, when somebody adds a sensible-looking wildcard to catch a few hundred old URLs and it quietly claims a page that was working fine.
Why there were 322 dead URLs
A 2025 rebuild moved this site off WordPress and did not bring the URL space with it. Every old address had been returning a 404 ever since. In August 2026 we exported the "Not found" report from Search Console: 322 URLs, dated 17 August.
Separately, we deleted a set of case study pages describing work that had not been delivered — the full account of that is a different piece — and collapsed four industry hubs into their service equivalents. Those pages needed to go somewhere too, and "somewhere" is not the homepage: a redirect to the root is a polite way of throwing a signal away.
The result is 178 rules: 139 exact paths and 39 wildcards, every one a 301. The file is in the repository, and every rule in it can be tested against this domain from a terminal.
The rule that eats a live page
Several old WordPress category prefixes collide with paths that are still live today. /website-development is the clearest case: it was a WordPress category, and it is currently the first segment of a portfolio archive at /website-development/projects.
Here is the rule anyone would write first:
1// The obvious rule. Every old /website-development/* article
2// now points at the topic that replaced it.
3{
4 source: '/website-development/:slug*',
5 destination: '/blog/topic/web-development',
6 statusCode: 301,
7}It does exactly what it says. It catches the hundreds of dead article URLs under that prefix — and it also catches /website-development/projects, which is a page that renders, ranks and is linked from the navigation. A 301 is a permanent instruction. Google will drop the archive and consolidate it into a blog topic page, and nobody will notice until the traffic is gone.
The rule
/website-development/:slug((?!projects$).*)→ /blog/topic/web-developmentTwo requests
/website-development/some-old-post301the wildcard claims it — /blog/topic/web-development
/website-development/projects200no rule matches — the live category archive renders
The fix is a negative lookahead in the path pattern:
1// The same rule, refusing to claim one path.
2// :slug must not be exactly "projects", so the live category
3// archive at /website-development/projects is never matched.
4{
5 source: '/website-development/:slug((?!projects$).*)',
6 destination: '/blog/topic/web-development',
7 statusCode: 301,
8}:slug((?!projects$).*) matches anything except exactly projects. Old article paths still redirect. The live archive is never matched, so it renders as itself. Fifteen category prefixes on this site needed the same treatment.
First match wins
The second trap is ordering. The redirect list is evaluated top to bottom and the first matching rule wins, which means a broad rule placed above a specific one silently makes the specific one unreachable.
The deleted case studies are exact paths that sit underneath /case-studies/*, and there are hub-level rules for that prefix too. If the hub rule were listed first, every individual case study would land on the hub instead of the industry page written to replace it — a worse destination, chosen by accident of line number. So the exact rules are assembled first, and the file says so in a comment, because the constraint is invisible in the data itself.
What a trailing slash costs
Old links carry trailing slashes. The framework normalises those itself before the redirect list is consulted, so a request for an old URL with a slash takes two hops rather than one.
- 308
/mobile-app/some-old-article/trailing slash normalised by the framework - 301
/mobile-app/some-old-articlethe redirect rule fires - 200
/blog/topic/mobile-app-developmentthe live page
Two redirects rather than one. Google follows both and passes the signals along, so this is a cost rather than a fault — but it is a cost paid on every link anyone ever wrote with a trailing slash.
This is a cost, not a bug — Google follows redirect chains of this length and passes the signals along. It is worth knowing about rather than fixing: writing slash and non-slash variants of 178 rules would double the file to save one hop.
Where this breaks
The list is only as good as the export it came from. Search Console reports the 404s it happened to crawl. A URL nobody has requested since the rebuild does not appear, so it is not in the file, and it will 404 the day someone finally clicks that old link.
Nothing tests it. There is no assertion anywhere that rule 140 still fires, that no rule shadows another, or that a destination still returns 200. Every check in this article is one I ran by hand. That is a habit, not a guarantee, and habits rot.
Destinations drift. A redirect pointing at a page that is later renamed becomes a chain, and a chain that grows becomes a loop. The file has no memory of which of its destinations are still real.
And we cannot tell you it worked. The honest position: this piece shows that the mapping is correct and testable. Whether the rankings held is a claim that needs a Search Console screenshot next to it, and that comparison is not something we are publishing today.
Test it against this domain
None of this needs taking on trust. These four commands run against production right now:
1$ curl -sI https://boffincoders.com/website-development/projects | head -1
2HTTP/1.1 200 OK
3
4$ curl -sI https://boffincoders.com/website-development/some-old-post | head -1
5HTTP/1.1 301 Moved Permanently
6
7$ curl -sIL https://boffincoders.com/mobile-app/some-old-article/ | grep -E 'HTTP|location'
8HTTP/1.1 308 Permanent Redirect
9location: /mobile-app/some-old-article
10HTTP/1.1 301 Moved Permanently
11location: /blog/topic/mobile-app-development
12HTTP/1.1 200 OKThe first two are the interesting pair: same prefix, same wildcard, one renders and one redirects. That is the exclusion doing its job.
Then run the equivalent against your own site. Take the ten most valuable URLs you have — the ones with links pointing at them — and check that each returns 200 and not a 301 into a topic page somebody added last month. The companion piece covers the version of this problem that happens during a framework migration, where the URLs move rather than being claimed.
Ready to Build Something
That Actually Works?
Stop patching legacy code. Let's engineer a platform that scales with your ambition.