Most migration losses come from the redirect map. Old URLs get missed, pattern rules send pages to the wrong place, legacy redirects stack into chains, and nobody checks what the server returns until rankings start to slide. The map itself is not difficult to build. It takes care, a sensible spreadsheet and a test run before launch. This is the method I would use, from an empty sheet to a map you can sign off.
It assumes you already know what kind of move you are making and why the map matters. If not, start with the website migrations guide, which covers the types of migration and where the map fits in the wider plan.
Set up the spreadsheet before you collect anything
Decide the columns first, so every URL lands in the same shape whichever source it came from. These are the columns I would use. Each one exists because someone later needs to filter or sort on it.
| Column | What goes in it | Why you need it |
|---|---|---|
| Old URL | The full old address, normalised | The key for every lookup and every test |
| Source | Where the URL was found: crawl, sitemap, analytics, Search Console, backlinks, logs, CMS | Shows which URLs only one source knew about |
| Template | Product, category, article, location, PDF and so on | Lets you write and check pattern rules per template |
| Clicks and sessions | Organic clicks from Search Console and sessions from analytics | Sets review priority |
| Linking domains | Count of referring domains from your backlink tool | Finds deep pages with link value |
| New URL | The single destination, or blank if the page is retired | The answer the developers build from |
| Match type | Exact, pattern, manual, parent, or retire | Tells reviewers how much to trust the row |
| Status to serve | 301, 308, 404 or 410 | Makes retirements a decision instead of an accident |
| Owner and notes | Who decided, and why | Stops the same argument happening twice |
| Test result | Status and final URL actually returned on staging | Filled in by the test run, never by hand |
Step 1: gather the old URLs from every source
Good starting points are your sitemaps, your server logs or analytics for the URLs with the most traffic, and your CMS, which can usually list every URL that hosts content.1 Add a full crawl and your backlink data to that. Each source misses something the others catch, and the URLs you miss are often the old ones with good links.
On a large site, pull Search Console page data through the Search Analytics API. A single API request returns up to 25,000 rows, with a default of 1,000, and you page through larger sets with the startRow parameter.2 Take the longest date range you can, because pages with seasonal traffic will not show up in the last three months.
Include images, PDFs and other downloads in the move too, since they may get search traffic or links of their own.1 They are easy to leave out because crawlers often skip them and analytics never records them as landing pages.
Before merging the lists, normalise every URL the same way: one protocol, one hostname, a consistent trailing slash, lower case if the old server treated case as equivalent, and tracking parameters stripped. Then remove duplicates and keep the Source column as a combined value, so a URL found by three sources shows all three.
Step 2: match old URLs to new ones in three passes
Working in passes lets you clear most of the sheet quickly and spend your time on the rows that need judgement.
Pass one: exact matches from the data
Most CMS exports carry an identifier that survives the move: a product SKU, a post ID, an article slug. Join the old export to the new one on that identifier and you have a confident match for every item that exists on both sides. A shop selling garden furniture, for example, can match every product on SKU even if the product URLs change from /p/12345 to /garden-furniture/oak-bench. Mark these rows Exact.
Pass two: pattern rules for whole templates
Where a template has a clean pattern, one rule can cover thousands of rows. A blog moving from /blog/2019/04/post-name to /articles/post-name is the usual example. Write the rule, then apply it to the spreadsheet and check that every resulting new URL exists on the new site. Rows where the generated URL does not exist are your exceptions, and they go to the manual pass.
Know how your server handles the rule. On Apache, the plain Redirect directive matches a prefix and appends whatever follows it to the target, so a rule for /service also redirects /service/anything. RedirectMatch takes a regular expression for exact control. Apache also processes these directives in the order they appear, with the first match winning, so put specific one-to-one rules above broad patterns.3 Other servers have their own rules. On nginx, a rewrite with the redirect flag returns a temporary 302 and one with the permanent flag returns a 301, while the return directive takes the status code explicitly.4 Read the documentation for your server or platform before writing any patterns.
Pass three: manual review by priority
Sort what is left by clicks, then by linking domains. Every row with meaningful traffic or links gets a person deciding where it goes. This is where merged pages, renamed categories and rewritten guides are handled. Where several old pages have been consolidated into one new page, redirecting all of them to it is fine.1 Where a page has been split, send it to the new page that covers its main search intent, and link to the others from there.
Step 3: decide what happens to pages with no equivalent
Every migration retires some pages: discontinued products, expired events, thin tag pages, old campaign landing pages. Each one needs an explicit decision in the Match type column. Redirecting many old URLs to one irrelevant destination such as the homepage can backfire, because Google might treat those redirects as soft 404s. Content you are not moving should return a 404 or 410.1
| Situation | What I would do |
|---|---|
| A product discontinued for good, with a category that sells close alternatives | Redirect to that category, marked Parent |
| An old article with links, replaced by a newer article on the same subject | Redirect to the newer article |
| A page with links but no relevant successor | Return 410, and decide whether the topic is worth recreating |
| Thin or duplicate pages with no traffic or links | Return 404 or 410 |
| A seasonal page that will return next year | Keep it on the new site, even if it is light for now |
Google’s crawlers treat a 404 and a 410 the same way: both tell Google the content does not exist, and an indexed URL is removed from the index.6 I would still use 410 for deliberate retirements, because it records intent in the server config and makes the retired rows easy to tell apart from mistakes in later reports. The HTTP standard draws the same line: a 404 does not say whether the absence is temporary or permanent, and a 410 is preferred when the server knows it is likely to be permanent.7
Step 4: choose the status codes and check the defaults
Moved pages need permanent redirects. In HTTP terms, 301 and 308 are the permanent redirects and 302 and 307 the temporary ones, and 307 and 308 also stop the client changing a POST into a GET.7 Google’s indexing pipeline treats a 301 or 308 as a signal that the target should be canonical. A 302 or 307 is followed, but not used as that signal.8 Server-side redirects are the most reliable kind, and JavaScript redirects are a last resort, for when server-side and meta refresh redirects are not possible.8
Defaults catch people out here. Apache’s Redirect directive issues a temporary 302 unless you add the permanent keyword or a 301 status.3 Many plugins and platform redirect managers have similar defaults. The only check that counts is the status code the server actually returns.
Step 5: test the whole map on staging
The URL Inspection tool suits individual URLs, and command line tools or scripts suit large numbers of them.1 For a migration you need the second. Request every old path in the sheet against the staging hostname, follow each redirect to the end, and write the results into the Test result column. A crawler can do the same job: Screaming Frog’s SEO Spider, in list mode with redirects always followed, exports every uploaded URL with its hops, loops and final status.9
The same failures appear later in Search Console’s Page indexing report, where a chain that is too long or a redirect loop shows as a redirect error, and pages that show a "not found" message without a 404 status code are reported as soft 404s.10 Catching them on staging is far cheaper than reading about them two weeks after launch.
After the bulk test, inspect a handful of the most valuable URLs one by one. Once the new site is live, the URL Inspection tool shows the canonical Google selected next to the one you declared, which is the quickest way to confirm that a redirect target is being treated as the canonical.11
Step 6: hand the map over and keep it
Give developers the map as data: an export of Old URL, New URL and Status to serve for the one-to-one rows, plus the pattern rules written out with their exception lists. Ask for the rules back as deployed, and rerun the full test against production within minutes of launch.
Then keep the sheet. The redirects themselves should stay for as long as possible, and generally at least one year.1 Google’s John Mueller explained the reasoning in 2021: its systems need to see a redirect a few times before they record the change.12 A migration guide on Bing’s webmaster blog suggests one to two years.13 Over that year you will need it to update internal links, to answer questions about why a page went where it did, and to check new 404s in the logs against what was planned. A 404 for a row marked Retire is expected. A 404 for a URL that is not in the sheet at all means the inventory missed something, and it can usually be added to the map the same day.
Common questions
01How many URLs should a redirect map include?
02Should I use 301 or 308 redirects in a migration?
03Is it ever acceptable to redirect old pages to the homepage?
04Should retired pages return a 404 or a 410?
Sources
- Platform docs11
- Regulator1
- Reporting1
- 01Site Moves and MigrationsGoogle Search CentralPlatform docs
- 02Search Analytics: queryGoogle for DevelopersPlatform docs
- 03mod_aliasApache HTTP Server DocumentationPlatform docs
- 04Module ngx_http_rewrite_modulenginx documentationPlatform docs
- 05Redirections in HTTPMDN Web DocsPlatform docs
- 06How HTTP Status Codes Affect Google's CrawlersGoogle for DevelopersPlatform docs
- 07RFC 9110: HTTP SemanticsIETFRegulator
- 08Redirects and Google SearchGoogle Search CentralPlatform docs
- 09How To Audit Redirects In A Site Migration Using The SEO SpiderScreaming FrogPlatform docs
- 10Page indexing reportSearch Console HelpPlatform docs
- 11URL Inspection toolSearch Console HelpPlatform docs
- 12Google: Keep 301 Redirects In Place For A YearSearch Engine JournalReporting
- 13Website Migration with BingBing Webmaster BlogPlatform docs
Written by Mani Bharij, SEO & AI Search Consultant in London. More on this subject in the Website migrations guide.