By the Brightcode team, a small web agency in Delhi NCR. Reviewed by Gaurav Khokkar, Founder.
Most rebuilds we see go wrong in the same boring place. The new site looks great, the Lighthouse scores are green, and three weeks later someone asks why enquiries from Google have dried up. Usually the old URLs are returning 404s, or they redirect to the homepage, or they bounce through three hops before landing anywhere.
The fix is a redirect map. Everyone knows that. What's less obvious is how to build one that's actually complete, and how to prove it works before DNS flips.
This is the process we follow when we redesign a site, move it to a new platform, or move it to a new domain. We went through it for our own old domain recently, so a few of these lessons are fresh.
Step 1: Collect old URLs from more than one source
The live sitemap is the first place people look, and it's usually incomplete. Old blog posts drop out of it, plugin pages never made it in, and campaign landing pages were never meant to be in it.
We pull URLs from at least these sources and merge them:
- The current XML sitemap (and any sitemap index children)
- A full crawl of the live site, with a desktop crawler or a small script
- The Pages report in Google Search Console, exported for the longest date range available
- Landing pages from analytics, so you catch URLs that get traffic but aren't linked anywhere
- Pages with external backlinks, from whatever backlink tool you have access to
- Ad and email campaign URLs, which marketing teams keep in spreadsheets you'll have to ask for
Then normalise everything before you deduplicate. Lowercase the host, pick one trailing slash convention, strip tracking parameters like utm_source, and decide what to do with www and http. Without this step you end up with five rows for one page.
Step 2: Decide a fate for every URL
Every old URL gets one of three outcomes:
- Redirect to the closest equivalent page on the new site
- Keep the same path, because the page still exists with the same purpose
- Retire with a 410 or a plain 404, because there's genuinely nothing equivalent
The tempting shortcut is to send everything you can't match to the homepage. Don't. Google tends to treat mass redirects to an unrelated page as soft 404s, and users who clicked a link about one specific thing land somewhere useless. An honest 404 page with good navigation is better than a misleading redirect.
"Closest equivalent" also means intent, not just title. Say an old page explained pricing and the new site has no pricing page. The services page that answers the same question is a better match than a blog post with a similar keyword in the slug.
Step 3: Keep the map in a boring format
Our redirect map is a CSV with four columns:
old_url,new_url,status,notes
/services/web-design/,/services/website-development,301,renamed service
/blog/old-post-slug/,/insights/new-post-slug,301,merged into new post
/summer-offer/,,410,campaign ended
A spreadsheet is fine for review with the client. But the source of truth should be a file in the repo, because then it can be tested in CI and the server config can be generated from it. Hand-typing hundreds of rules into a hosting panel is how typos ship.
A few rules we apply while filling it in:
- No chains. If page A already redirects to B, and B now moves to C, update A to point straight at C. Old redirects from previous redesigns are the usual culprit here.
- Point to final URLs. The target should return 200 directly, with the trailing slash and protocol your new site actually uses.
- Permanent means 301 or 308. Use 302 or 307 only for something genuinely temporary, like a maintenance page.
Step 4: Put redirects in the right layer
Where the rules live matters for speed and for who can maintain them later.
For a handful of pattern rules on a Next.js site, redirects() in next.config.js works well. Setting permanent: true returns a 308, which search engines treat the same as a 301.
For a long list of one-to-one rules, we prefer the edge or the web server. An Nginx map block generated from the CSV keeps lookups fast, and so do CDN-level bulk redirect lists. Both run before your app does any work, which keeps redirect latency low and means a broken app deploy won't take your redirects down with it.
On WordPress, a redirect plugin is fine for small sites, but for a big replatform we still prefer server rules generated from the same CSV. On Shopify, URL redirects can be imported in bulk from the admin with a CSV, which suits the map format above.
Whatever layer you choose, keep one place. Redirects split between a plugin, .htaccess, the CDN and the app are very hard to debug.
Step 5: Test the map like code
This is the step that gets skipped, and it's the cheapest one. We run a small script against staging, and again against production right after launch:
#!/usr/bin/env bash
# usage: ./check-redirects.sh redirects.csv https://staging.example.com
base="$2"
tail -n +2 "$1" | while IFS=, read -r old new status notes; do
first=$(curl -s -o /dev/null -w "%{http_code}" "$base$old")
read -r code url hops <<< "$(curl -s -o /dev/null -L --max-redirs 5 \
-w "%{http_code} %{url_effective} %{num_redirects}" "$base$old")"
target="$new"; [[ "$new" == /* ]] && target="$base$new" # full URLs for domain moves
if [ "$first" != "$status" ]; then
echo "FAIL $old: got $first, expected $status"
elif [ -n "$new" ] && { [ "$url" != "$target" ] || [ "$hops" -ne 1 ] || [ "$code" != 200 ]; }; then
echo "FAIL $old: ended at $url ($code) after $hops hops"
fi
done
For each row we check three things: the first response has the expected status, the final URL matches new_url, and it gets there in one hop with a 200 at the end. Anything else gets fixed before launch, not after.
It's also worth crawling the new site itself for internal links that still point to old paths. A redirect will catch them, but every internal link should go straight to the final URL. Menus, footers and links inside old blog content are where these hide.
Step 6: Update everything that points at URLs
Redirects handle people and bots arriving from outside. Inside the new site, a few more things need to agree with the map:
- Canonical tags point to the new URLs, never to redirected ones
- The XML sitemap lists only final, indexable URLs that return 200
-
hreflang, structured data and Open Graph URLs use the new paths -
robots.txton staging blocks crawling, and the production one doesn't (check this on launch day, every time)
For a domain move, there's one more step. Verify both domains in Google Search Console, make sure the old homepage 301s to the new one, and then use the Change of Address tool. Google's own guidance is to keep redirects in place for at least a year, so don't let the old domain lapse.
Step 7: Watch it for a few weeks
After launch we check the Search Console Pages report and crawl stats regularly, and look for new 404s in server logs. Old URLs you missed will show up there, often from backlinks nobody knew about. Add them to the CSV, regenerate the rules, re-run the test.
Rankings often wobble for a bit after a migration, even when everything is done right. What you want to avoid is the drop that never recovers because a whole section of URLs is quietly broken.
If you're still deciding whether a site needs a full rebuild at all, we wrote about when to rebuild versus redesign. And for the non-developer side of launch planning, like owners, access and tracking, our website migration checklist covers the rest.
What's the worst redirect mess you've inherited from a previous redesign? Tell us in the comments.
Written by the team at Brightcode, a Delhi NCR agency that builds websites and handles SEO-safe platform migrations.
Top comments (0)