Skip to content
AtomStorm
Workflowv1.0Published 2026-09-09Verified 2026-09-09

Rename a URL Without Losing the Traffic

The 301 is the easy part. The hard part is knowing which URLs disappeared — which needs an append-only ledger of what you actually shipped, a gate that reads the build product rather than the sitemap, and a redirect written in a form that does not fail silently. Method, checklist, ledger format, and the four source and destination forms that failed in our matrix — two of them named at parse time, one of them silent at every layer.

How this page was produced

Drafted by an AI agent. Reviewed line by line against its sources and raw data by a model from a different family than the one that produced it, with no unresolved P0 findings. Not yet read in full by a person. Evidence last verified 2026-09-09.

Rename a URL Without Losing the Traffic

Writing the 301 was never the hard part. Noticing that you owed one is.

Every guide to renaming a URL tells you the same thing: return a 301 from the old address to the new one. That advice is correct, complete, and almost useless — because it starts from a premise you cannot actually satisfy. It assumes you know which URLs disappeared.

On a static site you usually do not. A route file gets renamed, a slug gets tidied, a locale directory gets restructured, the build succeeds, and the diff shows only that a source file moved. Nothing anywhere says "the address /en/old-thing/ stopped existing." The 404s arrive weeks later, in a crawler report nobody opened.

One-line conclusion: the durable fix is not a redirect, it is a ledger — an append-only record of every URL you have ever shipped, kept outside the content that produces them, and a gate that compares that ledger against the build product on every build.

Who this is for — and who should skip it

For you if:

  • your site is statically exported and deployed as files, so "what we publish" is a directory you can walk;
  • URLs change more than never — new sections, renamed slugs, merged pages;
  • you have, or want, a build that can fail on your behalf.

Skip it if:

  • nothing is indexed yet — publish first, then start the ledger from your first real deploy;
  • your platform already keeps a durable route registry with change detection;
  • your URLs come from a database at request time, in which case the ledger idea still holds but the enumeration step is a query, not a directory walk.

The three ways a rename goes wrong

FailureWhat it looks likeWhy the obvious fix misses it
Silent disappearanceOld URL 404s, nobody notices for weeksNothing in the diff says a URL went away — only that a file moved
301 into a 404The redirect fires, the target does not existThe rule was written from the plan, not checked against the build product
Silently dead ruleThe rule is written, deployed, counted as valid, and never matchesThe platform can accept the syntax and say nothing

The three need three different defences, and only the first one is what most advice is about.

The method

Step 0 — Start the ledger before you need it

Before the first rename, record what you currently publish. Ours is a plain text file of site-relative paths, one per line, seeded from the real build output on the day the gate went in: 39 URLs — 36 pages plus 3 feeds.

Two properties matter more than the format:

  • Site-relative, not absolute. We plan to reuse this build against a second origin by changing one environment variable; an absolute-URL ledger would be void the day that happens.
  • Append-only. A line is added when a URL is first published and is never removed. Deleting a line is discharging an obligation, and that should be a visible diff someone can object to — not a side effect of a script.

Step 1 — Derive "published" from the build product, once

The ledger is only as good as the definition of published. Two things need that definition — the gate that checks it and the tool that appends to it — and two implementations of the same definition will drift. A drifted definition does not fail loudly; it invents disappearances that never happened. Ours lives in one module both sides import.

The definition itself carries the second load-bearing decision:

Published means "exists in the build output", not "appears in the sitemap." A page can leave the sitemap and still serve 200. On our site a fallback page — one whose canonical points at the other language — is excluded from the sitemap by design, while continuing to answer requests normally. It owes nobody a redirect. Judge by sitemap membership and you manufacture phantom disappearances on exactly the pages that are working correctly.

Step 2 — Write the redirect in a form that actually runs

This is where the third failure mode lives, and it is the one you cannot reason your way out of — you have to measure it. On our host, _redirects is a plain text file, which makes it feel like the rules either work or throw. They do not.

We ran one experiment on a local Pages simulator (wrangler 4.65.0) — a matrix of 48 rule forms, each requested 3 times, 144 requests, with four controls declared in advance, all of which passed. Two source forms never worked:

Source formResultWas there any warning?
Relative path /a/ or splat /a/*Works — 22 of 24 rules; the other 2 failed for their destination, not their source— (the 2 that failed were named at parse time)
Absolute URL https://host/a/12 of 12 rules, all 36 requests: 404Yes — named at parse time with a line number
Protocol-relative //host/a/12 of 12 rules, all 36 requests: 404No — 11 of those 12 produced no signal at any layer

Those results are about a rule's source. Destinations fail their own way: in our run a cross-origin destination with status 200 did not proxy — the request fell through to a 404, and the parser named it. The destination table is in the asset at the end of this page.

The protocol-relative form is the dangerous one. It begins with /, so it passes a "must be a relative path" check — and in our run it was counted among the valid rules, never warned, and never matched. Written, accepted, silent, dead.

Scope limit, stated here and not in a footnote: those numbers are from a local simulator, not a production edge. We do not claim a production edge behaves the same way. The reason to avoid these forms anyway is not the measurement — it is that betting a migration on behaviour the vendor's documentation does not specify is a bad trade regardless of which way the measurement went.

Step 3 — Rebuild, and let the gate find the disappearance

Now the mechanical part. On every build, walk the ledger:

  1. Still in the build output? Nothing is owed. Continue.
  2. Gone? There must be a matching redirect rule.
  3. The rule must be a 301, not a 302 — a rename is permanent, and a temporary redirect tells crawlers to keep the old address.
  4. The target must exist in the build output, or you have shipped a 301 into a 404. (A deliberately cross-origin target is the one exception: the build cannot check another origin, so that case is accepted as declared.)

A newly published URL is not an error. The ledger is allowed to lag behind the site; only disappearance without a 301 is a failure. Our gate prints the outstanding count on every build so the lag stays visible instead of becoming invisible debt.

Step 4 — Append, deliberately, as a separate act

Extending the ledger is a command a person runs, and it is deliberately not part of the build. The reasoning is short:

A verifier that rewrites its own expectations can only ever get greener.

If the build both checked the ledger and updated it, every rename would be self-approving. So the build reads; a human writes. The append step only ever adds lines, and it refuses to run at all if the build output enumerates zero URLs — because a broken walker looks exactly like a site that published nothing.

Step 5 — Keep the two lists apart

We keep two URL lists, and conflating them would break both:

Frozen historyLive ledger
Holds12 URLs from before one specific migrationEvery URL ever published; 39 at seeding, more since
Changes?NeverGrows, never shrinks
Derived fromA captured baseline sitemap, plus 2 feed URLs added by handThe real build output

Neither is derived from the content directory, and for the frozen list that rule has teeth: a page created after a migration never had an old URL, so deriving the "old URLs" list from current content would make the check assert that today's pages redirect to themselves. Tautology, dressed as a gate.

Step 6 — Prove the gate still bites

A green gate means nothing went red; it does not mean the gate is still watching. Before trusting ours, we broke it on purpose — four state transitions plus three controls:

Injected stateExpectedObserved
Delete a published page, no ruleRedRed — named the URL and the missing rule
Add the rule as a 302RedRed — "a rename must be a permanent 301"
Point the 301 at a nonexistent targetRedRed — "a 301 into a 404"
Point the 301 at a real target#23 greenGreen
Remove one line from the ledger (new page, not yet recorded)Pass with noticePassed, printed the notice
Empty the ledgerRedRed — "every check below would pass vacuously"
Rewrite the ledger to another site's pathsRedRed — "shares 0 URLs with the build output"

The last two are the anti-vacuous controls, and they matter more than they look: a check that iterates an empty list passes perfectly and guards nothing.

One honest detail from that run. When we applied the correct fix, the gate under test went green — and the overall run still exited non-zero, because a different check had gone red: moving a page out of the build output had broken a sitemap-to-page count invariant. Our first write-up attributed that failure to the wrong check, from inference. Re-running and reading the full output corrected it. The rule we took away is the one this whole page runs on: read the output, then attribute — never the reverse.

What we verified — and what we did not

  • Verified: the gate, the shared definition, the append-only tool and both URL lists are running in this repository; the seven injected states above produced the outcomes shown; the redirect-form matrix was really run and its four controls were declared in advance and all passed.
  • Not verified: that our ledger has ever caught an accidental rename. Every failure we have shown it was one we injected. It has been running since 2026-09-09.
  • Not verified: production-edge behaviour for any redirect form. One local simulator, one experiment, no independent replication.
  • Not ours to verify: whether search engines pass the signal you want through a given redirect. Follow the platform documentation in the sources below; our gate only guarantees the redirect exists, is permanent, and lands somewhere real.

Limitations

The ledger guards addresses, not meaning — move a page and hollow it out and every check here still passes. It assumes a walkable build product. Its redirect findings come from a single local run. And it has, so far, only ever caught faults we planted ourselves.

Take the assets

AssetWhat it is
Rename checklist16 binary gates: before the rename, writing the redirect, after the build, and two on discipline
URL ledger formatThe file format, the append rule, and the four checks to run against it
Redirect forms that fail silentlyThe measured matrix, what warned and what did not, and the scope limit

The next action

Do Step 0 today, before you need it. Enumerate what your build currently publishes, commit that list, and write down the one sentence that defines what "published" means for your site. Everything else on this page is mechanical once that file exists — and impossible to retrofit honestly once the first URL has already vanished.

Sources