Preserving Links Through a Site Migration
A replatform is where sites lose their link history. Not gradually — in one afternoon, when a new URL structure goes live and several years of earned links start resolving to 404s.
The mechanism is always the same: the redirect map was treated as a launch task rather than as the migration itself.
Build the map from the right list
The instinct is to map “all our pages.” That’s the wrong source list, because it’s the list of URLs you know about, and it omits the ones that matter most: URLs that no longer appear in your CMS but still have links pointing at them.
Build the source list by union, from four places:
- Every URL in the current CMS or route table. The obvious one.
- Every URL with external links, from a backlink export. This is the critical addition — it includes pages deleted years ago whose links are still live, and those are precisely the ones nobody thinks to map.
- Every URL with impressions or clicks in the last 12 months, from Search Console. Catches indexed pages the CMS forgot.
- Every URL requested in the last 90 days, from server logs, above some minimal request threshold. Catches everything else, including paths from old campaigns and printed materials.
Deduplicate, normalise variants, and you have the real surface area. It’s usually substantially larger than the CMS list.
sort -u cms-urls.txt backlink-urls.txt gsc-urls.txt log-urls.txt > migration-sources.txt
wc -l migration-sources.txt
Then prioritise. You will not hand-map ten thousand URLs, and you don’t need to — sort by external links first, then by organic clicks. The top few hundred deserve individual attention; the tail can be handled by pattern rules.
Map to equivalents, not to approximations
Every source URL needs a target, and the target should be the page that serves the same purpose.
Where there’s no equivalent, the choice is between the nearest genuinely relevant page and an honest 404. Do not default everything unmatched to the homepage. A redirect to an irrelevant page is treated much like a 404 by search engines — a soft 404 — so it preserves nothing, and it’s worse for users than an error page because it discards their intent silently. A 404 with good navigation is a legitimate answer for content that genuinely no longer exists. Use 410 if you want to be explicit that the removal is permanent.
Where one old page splits into several new ones, pick the closest and accept the imprecision. Where several old pages merge into one, they all point at the merged page — that’s a clean case.
Record the map as data, not as config:
old_path,new_path,type,notes
/blog/2019/seo-basics,/guides/seo-basics/,301,72 referring domains — verify first
/products/widget-blue,/shop/widgets/blue/,301,
/about-us,/about/,301,
/promo/spring-2021,,410,campaign ended, no equivalent
A CSV is testable, diffable, reviewable, and can be generated into whatever format the target platform needs. Config written by hand in a template is none of those things.
Pattern rules for the tail
Most of the tail follows patterns. Write rules, but write them carefully and test them against the real source list rather than against imagination.
# nginx — dated blog paths to flat guide paths
location ~ ^/blog/\d{4}/(?<slug>[a-z0-9-]+)/?$ {
return 301 /guides/$slug/;
}
Two rules for pattern rules:
- Run every source URL through the ruleset offline before launch and check the outputs. A script that reads
migration-sources.txt, applies the rules, and prints anything that produces no match, a self-referential match, or a target that isn’t in the new site’s URL list will catch nearly everything. - Specific rules before general ones. The hand-mapped high-value URLs must be matched by their own rules, not swallowed by a pattern.
Launch day, in order
- Redirects deploy with the new site, not after. Not “the same day” — in the same release. The window between the new structure going live and the redirects landing is exactly when crawlers hit the 404s.
- Verify the top 100 immediately. Automated, from a list, not by clicking.
- Submit the new sitemap; keep the old one available briefly. It helps the crawler discover the old URLs and find their redirects. Remove it once the redirected URLs have been recrawled.
- Watch Search Console’s page indexing report daily for two weeks. The signal you’re looking for is a rise in “Not found (404)” — every entry there is a URL your map missed. Add it and redeploy.
- Watch server logs for 404s with a referrer. A 404 with an external referrer is a live inbound link arriving nowhere. Highest priority in the whole process.
- Leave the redirects in place permanently. They cost nothing and they’re load-bearing for as long as the old links exist, which is indefinitely.
Verification you can automate
# expected.csv: old_path,new_path
while IFS=, read -r old new; do
final=$(curl -sIL -o /dev/null -w '%{url_effective} %{http_code}' "https://example.com$old")
case "$final" in
*"$new 200") ;; # correct
*) echo "MISMATCH $old -> $final (expected $new)" ;;
esac
done < expected.csv
Run it before launch against a staging host and after launch against production. Add hop counting — -w '%{num_redirects}' — and flag anything above one, because migration day is when chains are born: the new rules point at URLs that some older rule then redirects again.
The failures worth naming
Redirecting to the homepage in bulk. Covered above. It’s the single most common migration mistake and it converts a mapping problem into a total loss.
Forgetting non-page URLs. Old sitemap locations, RSS feed paths, image URLs that were hotlinked, PDF downloads that were cited. Feeds especially — a dead feed URL silently disconnects every subscriber.
Changing URL structure and platform simultaneously when you don’t have to. If the replatform doesn’t require new URLs, keep the old ones. Every URL you don’t change is a URL you can’t break. This is the cheapest risk reduction available and it gets skipped because new platforms come with opinionated defaults.
Breaking the protocol/host/slash convention. A new platform defaults to trailing slashes where the old one didn’t, and now every mapped redirect is followed by a normalisation redirect. Decide the convention first, then build the map to match it. See redirect chains and how to flatten them.
Leaving canonicals pointing at old URLs. Templates carrying hardcoded canonical logic from the old structure will contradict the redirects. Check with a crawl, not by reading the template — canonical tags are a hint, not a command.
Blocking the new site in robots.txt. The staging Disallow: / shipping to production is a genuinely common launch-day incident. Check it first, before anything else, because everything downstream depends on it.