Print it, send it, tick it off. Nothing here is optional.

Migrations do not fail on the framework. They fail because somebody skipped a step that looked administrative, and then nobody noticed for six weeks because the site looked fine the whole time.

Use this on whoever does the work, including me. If a developer cannot answer these, you have learned something worth knowing before you pay them.

Before anyone writes code

Everything in this block is your job, not the developer's, and every item left undone becomes a delay in week three.

Access and decisions

  • Hosting login, DNS access, domain registrar login, all handed over in a password manager.
  • Search Console verified and access granted to whoever is doing the work.
  • Analytics access granted, with the property and view named explicitly.
  • One person named as the decision maker, with authority to approve deleting a page.
  • A full database and file backup taken, and confirmed to restore.
  • Staging environment available, on a subdomain that is blocked from indexing.
  • Agreed in writing that the design is frozen and this project is a migration, not a redesign.

Inventory

  • Every active plugin listed, each marked replace, drop or keep.
  • Every custom post type and field group listed.
  • Every distinct page template counted, because this drives the build cost more than page count.
  • Content audit done: which pages earn traffic, which are dead, which get deleted.
  • Any page under legal or regulatory obligation flagged so it never gets deleted.

Freeze the URLs

This is the step that protects your rankings, so it gets its own block.

Pull the URL list from three places, not one. Your sitemap knows what you think you publish. Search Console knows what Google indexed. Server logs know what still gets requested, including pages that fell out of the sitemap years ago and still earn traffic.

terminalbash
# 1. From the sitemapcurl -s https://example.com/wp-sitemap.xml \  | grep -oE '<loc>[^<]+</loc>' | sed 's/<[^>]*>//g' \  | while read -r m; do curl -s "$m" | grep -oE '<loc>[^<]+</loc>'; done \  | sed 's/<[^>]*>//g' | sort -u > urls-sitemap.txt # 2. From Search Console: export the Pages report, then take column oneawk -F',' 'NR>1 {print $1}' search-console-pages.csv | sort -u > urls-gsc.txt # 3. From the access log: anything that returned a 200 in the last 90 daysawk '$9 == 200 {print $7}' access.log | sort -u > urls-logs.txt # The union is what the redirect map has to covercat urls-*.txt | sort -u > urls-all.txtwc -l < urls-all.txt # And the interesting part: what the logs know that the sitemap does notcomm -13 urls-sitemap.txt urls-logs.txt | head -40

That last command is the one that earns its keep. It usually returns something somebody forgot about.

Routing

  • URL list built from sitemap, Search Console and server logs, then merged and deduplicated.
  • Every old URL mapped to a new one, or explicitly marked for deletion with a reason.
  • Redirects are 301 or 308, permanent rather than temporary.
  • No redirect chains. Old URL goes to final URL in one hop, not through two others.
  • Trailing slash behaviour decided and applied consistently across the whole site.
  • Query string URLs handled, including the old style paths if permalinks ever changed.
  • Uppercase and mixed case variants covered if the old server was case insensitive.
  • Paginated archive URLs mapped, not just the first page.

While the build runs

Content and templates

  • Templates built against real content, never placeholder text, which hides layout problems.
  • Images carried across with alt text intact, not regenerated empty.
  • Internal links inside post bodies updated to the new paths.
  • Category and tag pages rebuilt or deliberately dropped with redirects.
  • Author pages handled, since these are often noindex and do not need rebuilding.
  • Any shortcodes in post content either rendered or stripped, with a decision recorded.

Metadata

  • Titles carried across, with the site name appearing exactly once rather than twice.
  • Meta descriptions carried across per page.
  • Canonical tags pointing at the new domain, one per page, no duplicates.
  • Robots rules preserved, so pages that were noindex stay noindex.
  • Open Graph and Twitter card images working, with a site wide fallback for anything missing.
  • JSON-LD rebuilt, including the breadcrumb markup your SEO plugin was emitting.
  • Sitemap generated at the new site and pointing only at live, indexable URLs.
  • robots.txt reviewed and pointing at the new sitemap.

The pre launch pass

Do all of this on staging while the live site is still serving. This is the last cheap moment to find a problem.

Verify on staging

  • Every redirect tested automatically, with the failures fixed and the test rerun until clean.
  • Every meta tag diffed against the live site, with each difference explained out loud.
  • Staging blocked from indexing, and confirmed blocked before anyone shares the link.
  • Forms submitted end to end, with the email confirmed as arriving.
  • Site search working, if you are keeping it.
  • Analytics firing, with the property confirmed as the right one.
  • 404 page returning an actual 404 status, not a 200 with an error message on it.
  • Tested on a real mid range phone, not a resized desktop window.
  • Tested with a screen reader or at minimum a keyboard, tabbing the whole way through.
  • Core Web Vitals checked on the templates that matter most.
terminalbash
# Should print 404. If it prints 200, you have a soft 404 problem.curl -s -o /dev/null -w '%{http_code}\n' https://staging.example.com/definitely-not-a-real-page # Confirm staging is genuinely blocked before you send the link to anyonecurl -s https://staging.example.com/robots.txtcurl -s https://staging.example.com/ | grep -i 'name="robots"'

Launch day

The switch

  • Launching on a Tuesday or Wednesday morning, never a Friday and never before a campaign.
  • DNS TTL lowered a day or two beforehand so a rollback propagates quickly.
  • A rollback plan written down, with the person who can execute it available.
  • SSL certificate valid on the new host before DNS points at it.
  • The old site left running until DNS has fully propagated.
  • Staging noindex rules removed from production, so the live site is actually indexable.
  • A crawl run against production immediately after the switch to catch broken links.
  • New sitemap submitted in Search Console.
  • Change of address tool used, but only if the domain itself changed.

That second to last one about noindex is the single most expensive mistake in this article. Shipping a staging noindex rule to production hides your entire site from search. It has happened to sites much larger than yours.

terminalbash
# Run this the moment DNS moves. Both should come back empty.curl -s https://example.com/ | grep -i 'noindex'curl -s https://example.com/robots.txt | grep -i 'Disallow: /$'

The first month after

The project is not finished when DNS moves. Crawl errors and coverage changes surface over days and weeks, which is why a fortnight of watching belongs inside the scope rather than being a favour afterwards.

Week one

  • Search Console coverage checked daily for new errors.
  • Server logs watched for 404s coming from real traffic, then patched into the redirect map.
  • Analytics compared against the same period last month, allowing for a short dip.
  • Any page that lost its ranking checked individually rather than panicking about the total.

Weeks two to four

  • Coverage checked weekly instead of daily.
  • Core Web Vitals field data reviewed, once enough real visits have accumulated.
  • Internal links audited for anything still pointing at an old path.
  • Backlinks spot checked, and the highest value ones confirmed as landing correctly.
  • Redirects confirmed as staying in place for at least a year.

Handing this to a developer

If you are commissioning rather than building, this is the part to use as a filter. Send the whole article and ask four questions.

  • Which of these do you do as standard, and which are extra? A straight answer here tells you what is really in the quote.
  • How do you test the redirect map? If the answer is by hand or by spot checking, the map will have holes.
  • What happens in the two weeks after launch? If the engagement ends at DNS, you are buying a build rather than a migration.
  • Who owns the repository, and can I hire someone else to take it over next year? The answer should be you, and yes.

You do not need a developer who says yes to everything. You need one who tells you which items do not apply to your site and why.

Related readingMoving a WordPress site to Next.js without losing your rankings

The method behind the routing and metadata checks is written up in full there, with the scripts. If you want to know what this costs and how long it takes before you commit, those have their own pieces.

Related readingWhat a WordPress to Next.js migration costs, line by line

I work through exactly this list on every migration I take, and the redirect and metadata checks run automatically rather than by eye. If you would rather hand the whole thing over, the migration page covers scope and timelines, and the contact form gets you a straight answer on whether your site is worth moving at all.

Resources