Skip to content

A website migration checklist, filled in

Example

Penhallow Garden Supply is a made-up nursery and garden shop moving off WordPress and WooCommerce. This is the checklist we’d run for that move, phase by phase, with the verification log and a sample redirect map you can copy.

Example. Penhallow Garden Supply is fictional. URLs, counts and dates are invented to show the shape of the work. The checklist is a template for your own migration, not a record of a real one.

What a migration has to protect

A safe website migration comes down to four things: a full list of what you have before you start, a redirect from every old URL to its new home, content and customer data checked rather than assumed, and a cutover in one planned step with a way back. Rankings survive when nothing that search engines know about goes missing.

Below is the checklist we work from, filled in for a fictional garden shop. The tick boxes are as they stood at the end of the build, two weeks before cutover. The verification log and the redirect map follow it, and the map is also a CSV you can copy.

Moving from
WordPress 6 with WooCommerce, 23 plugins, shared hosting
Moving to
Next.js on Vercel, Sanity for content, headless Shopify for products, orders and customers
Cutover
Tuesday 9 February 2027, 6am
Owner on Penhallow’s side
Morwenna Trevail, Operations

6am is the quietest hour in the shop’s analytics, and February is outside the spring rush.

Phase 1: audit

What we have, and what has to survive.

URLs

  • Done: Crawl of the live site saved (every URL, status code, title, canonical): 1,416 URLs.
  • Done: Sitemap URLs compared with the crawl. 61 URLs in the sitemap weren’t linked from anywhere; listed for a decision.
  • Done: Search Console export: every URL with impressions in the last 16 months.
  • Done: Backlink export: every URL with external links pointing at it.
  • Done: Analytics export: top 200 landing pages by sessions over 12 months.
  • Done: The URLs that matter merged into one list: anything with traffic, impressions, links or orders. 402 URLs.

Content

  • Done: Counts by type: 640 products, 38 product categories, 212 blog posts, 24 pages, 9 plant care guides, 4,800 media files.
  • Done: Content that won’t move, agreed with Morwenna: 140 discontinued products with no traffic and no links (allowed to return 410 Gone; discontinued products that do have traffic or links get a row in the redirect map), and 31 thin tag archives (redirected to the blog).
  • Done: Shortcodes and page builder blocks listed, with what each becomes in Sanity.

Shop data

  • Done: Customers: 11,300 accounts. Passwords can’t move; customers will be invited to set a new one.
  • Done: Order history: 38,000 orders, to be imported into Shopify for customer accounts and reporting.
  • Done: Open orders on cutover day: process to finish them in WooCommerce agreed (see Phase 4).
  • Done: Gift cards and store credit: 212 active balances, to be recreated in Shopify. Keeping the same codes depends on the Shopify plan; if it can’t, holders get new codes by email on cutover day.
  • Done: Subscriptions: “seed of the month”, 340 subscribers. Moving to a Shopify subscription app; re-consent emails drafted.
  • Done: Reviews: 2,900 product reviews, moving to the Shopify reviews app with dates and names kept.

Integrations

  • Done: Every plugin listed with what replaces it, or why it isn’t needed (23 plugins: 9 replaced, 14 dropped).
  • Done: Delivery: local delivery zones by postcode and the courier integration.
  • Done: Email: Mailchimp audience and the abandoned basket flow.
  • Done: Accounting: Xero sync.
  • Done: Analytics and Tag Manager container.

What ranks

  • Done: Top 100 queries and the page each one lands on, recorded with average positions as the baseline.
  • Done: Structured data on the current site recorded (products, breadcrumbs, reviews).

Phase 2: plan

The written plan

  • Done: New URL structure agreed. Products move from /product/<slug>/ to /plants/<slug> or /tools/<slug>; categories from /product-category/<a>/<b>/ to /<a>/<b>.
  • Done: Redirect map written for all 402 URLs that matter, plus pattern rules for the long tail (see the sample below).
  • Done: Data mapping written: every WooCommerce field to its Shopify or Sanity field, including custom fields (hardiness zone, sowing months, pot size).
  • Done: Cutover date chosen and agreed: Tuesday 9 February 2027, 6am.
  • Done: Rollback plan written: DNS back to the old host. The old site stays up for 30 days, closed to new orders, while open orders are finished there.
  • Done: Freeze dates agreed: content freeze on the old site from Friday 5 February; orders keep flowing until cutover.
  • Done: Fixed price agreed in writing. (This example leaves the figure out.)

Phase 3: build alongside the old site

Staging

  • Done: New site built on a staging domain with noindex and password protection.
  • Done: Real content loaded early (week 2), so problems show up in demos, not at launch.
  • Done: Products imported into Shopify; images re-exported at full resolution from the originals, not the resized WordPress copies.
  • Done: Every product page checked against the old one by script: title, price, stock, variants, description length. Differences listed and fixed.
  • Done: Titles and meta descriptions carried across; rewritten only where the old one was missing or duplicated.
  • Done: Structured data on product, category, article and breadcrumb templates; validated.
  • Done: Canonicals point at the new URLs on the live domain, not the staging domain.
  • Done: Internal links in blog posts rewritten to new URLs, so they don’t rely on redirects.
  • Done: Redirect map loaded and tested on staging: every row returns one permanent redirect to a 200 page, with no chains.
  • Done: Checkout tested end to end with test card payments in Shopify’s test mode: delivery zones, gift cards, discount codes, subscriptions.
  • Done: Xero sync tested with test orders.
  • Done: Accessibility and device testing.
  • To do: Customer re-invite email signed off by Morwenna.
  • To do: Order import dry run on a copy of the live data, counts reconciled.

Phase 4: cutover day

Before 6am

  • To do: Final order import of everything placed since the dry run.
  • To do: Old site set to accept no new orders at 5:45am, with a “back in a few minutes” banner.
  • To do: Open orders exported; the old site stays up for 30 days, closed to new orders, while staff finish them in WooCommerce’s admin.

6am

  • To do: DNS switched to the new site (TTL lowered to 5 minutes the week before).
  • To do: noindex removed and password protection off on production.
  • To do: Redirects live on the production domain.

First hour

  • To do: Every row of the redirect map tested against the live domain by script.
  • To do: Ten real URLs from the backlink list tested by hand.
  • To do: Test order placed, refunded, and checked in Shopify and Xero.
  • To do: New sitemap submitted in Search Console; old sitemap left in place for two weeks so search engines find the redirects.
  • To do: Error monitoring watched for 404s and checkout errors.

Go or roll back by 8am. If checkout or redirects are failing and can’t be fixed by 8am, DNS goes back to the old site and we try again another morning.

Phase 5: the 90 days after

Checks for the 90 days after cutover
WhenCheckAction if it fails
Daily, days 1 to 14404 report from server logs and Search ConsoleMissed URL added to the redirect map
Daily, days 1 to 14Checkout errors, failed payments, Xero syncFixed; Morwenna told
Day 3Search Console: new sitemap read, pages being indexedResubmit; check for noindex or canonical mistakes
Days 1 to 7Customer re-invite emails, sent in batchesPause batches if the support queue backs up
Days 7 and 14Subscription re-consent remindersMorwenna calls anyone left by day 21
Weekly, weeks 1 to 6Baseline queries from Phase 1: position and clicks, page by pageAny page down more than a few places for two weeks running gets its redirect, title, content and internal links checked
Weekly, weeks 1 to 6Indexed pages count: old URLs dropping out, new ones coming inInvestigate any old URL still indexed after four weeks
Day 30Old hosting: has anything needed it?Archive database and media, then cancel
Day 30Core Web Vitals on the new templates, lab and field where availableFix list for any template outside the “good” range
Monthly, months 2 and 3Search Console clicks and impressions against the same months last yearWritten note to Morwenna either way
Day 90Close-out: remaining 404s, ranking comparison, redirect map frozenPattern rows reviewed; redirects stay in place permanently

Some movement in rankings while search engines recrawl is normal. A lasting drop usually means something was missed, which is what the 404 report and the weekly comparison are there to catch.

Verification log

Every test is logged with when it ran and what it found. This extract covers the staging run on 26 January 2027 and the first hour of cutover.

Verification log
Date and timeCheckResultNotes
26 Jan, 10:12All 402 mapped URLs against staging396 pass, 6 fail4 chains (old URL to old URL to new), 2 to a 404
26 Jan, 14:406 failures fixed, re-run402 passChains collapsed to one hop
26 Jan, 15:051,416 crawled URLs against pattern rules1,416 resolve as plannedNone to a 404. The 140 agreed products return 410 Gone; 31 tag archives go to /journal
26 Jan, 15:30Product parity script, 500 products497 match3 had prices changed in WooCommerce since import; re-imported
27 Jan, 09:00Test checkout, 12 scenarios12 passDelivery zones, gift card, discount code, subscription
9 Feb, 06:04DNS resolves to new hostPassChecked from three networks
9 Feb, 06:10All 402 mapped URLs against production402 pass
9 Feb, 06:1810 backlink URLs by hand10 pass
9 Feb, 06:25Live test order, refund, XeroPassOrder and refund both in Xero by 06:40
9 Feb, 06:45Sitemap submittedPass
9 Feb, 07:50Go or roll backGoNo checkout errors; two 404s from an old newsletter, added to the map

A sample redirect map

30 rows of the 402, plus the pattern rules. All redirects are permanent (308). Specific rows always win over pattern rules. Marketing parameters (utm_*, gclid, fbclid) are carried through to the destination on every row, so campaign tracking survives; every other query string is handled by a row of its own or dropped on purpose.

The sample is also a spreadsheet you can copy, with no sign-up: download the redirect map as a CSV.

Specific rows

Redirect map: specific rows
#Old URLNew URLCaseSourceNotes
1/shop//shopTrailing slashCrawlTrailing slashes dropped site-wide; the new site serves one version only
2/product-category/plants//plantsPath changeSearch ConsoleTop category by impressions
3/product-category/plants/perennials//plants/perennialsPath changeSearch Console
4/product-category/plants/perennials/page/2//plants/perennials?page=2PaginationCrawlPage numbers kept, so deep links to page 2 and 3 still land on the same products
5/product-category/plants/perennials/page/3//plants/perennials?page=3PaginationCrawlAs row 4
6/product-category/tools/hand-tools//tools/hand-toolsPath changeAnalytics
7/product/hydrangea-annabelle-3l//plants/hydrangea-annabelle?size=3lMerged variantsBacklinksPot sizes are now variants of one product; the size parameter preselects the right one
8/product/hydrangea-annabelle-5l//plants/hydrangea-annabelle?size=5lMerged variantsCrawlAs row 7
9/product/hydrangea-annabelle-10l//plants/hydrangea-annabelle?size=10lMerged variantsCrawlAs row 7
10/product/felco-2-secateurs//tools/felco-2-secateursPath changeBacklinks14 linking domains
11/product/peat-free-compost-50-litre//garden-care/peat-free-compost-50lSlug changeAnalyticsSlug shortened and moved category
12/product/lavender-hidcote-9cm-pot//plants/lavandula-hidcoteSlug changeSearch ConsoleRenamed to the botanical name Morwenna wants used; old common name kept in the title
13/product/blue-glazed-pot-30cm//pots/glazedDiscontinuedCrawlNo stock since 2025, no direct replacement; to its category
14/product/rose-feed-1kg//garden-care/rose-feed-2kgDiscontinuedBacklinksReplaced by the 2kg pack; closest real equivalent
15/?p=1843/journal/when-to-plant-dahliasQuery string IDBacklinksWordPress short link, used in old newsletters
16/?page_id=27/help/deliveryQuery string IDCrawlOld default page link still in a supplier’s footer
17/?product=felco-2-secateurs/tools/felco-2-secateursQuery string IDCrawlWooCommerce’s plain-permalink product link
18/?s=hydrangea/search?q=hydrangeaSearchAnalyticsCovered by the pattern rule below; listed because it’s the top internal search
19/2023/04/when-to-plant-dahlias//journal/when-to-plant-dahliasPath changeSearch ConsoleTop blog post by impressions
20/2024/09/autumn-bulb-planting-guide//guides/autumn-bulbsMerged contentBacklinksMerged into the bulb guide
21/2024/10/autumn-bulbs-part-2//guides/autumn-bulbs#naturalisingMerged contentCrawlTo the section it became
22/plant-care//guidesPath changeCrawl
23/delivery-information//help/deliveryPath changeAnalytics
24/my-account//accountPlatform routeCrawlShopify customer accounts
25/cart//basketPlatform routeCrawl
26/checkout//basketPlatform routeCrawlCan’t resume a WooCommerce checkout; to the basket instead
27/wp-content/uploads/2024/03/penhallow-seed-catalogue-2024.pdf/files/seed-catalogue-2024.pdfMediaBacklinks9 linking domains, including two gardening clubs
28/wp-content/uploads/2025/02/planting-calendar.pdf/guides/planting-calendarMedia to pageSearch ConsolePDF replaced by a web page; ranks for “cornwall planting calendar”
29/wp-content/uploads/2023/06/hydrangea-annabelle-1024x768.jpg/plants/hydrangea-annabelleMediaImage searchImage ranking in Google Images; to the product rather than a bare image
30/feed//journal/rss.xmlFeedCrawlTwo feed readers still subscribed

Pattern rules, applied after the specific rows

Redirect map: pattern rules
Old patternNew URLCaseNotes
/:path*//:path*Trailing slashStrips the slash, then the specific rows apply. One hop: the rule is merged with the row, never chained
/YYYY/MM/:slug//journal/:slugDated postsEvery remaining blog post
/tag/:tag//journalThin archives31 tag pages with no traffic
/product-category/:a/:b/page/:n//:a/:b?page=:nPaginationAny category page not listed
/?s=:q/search?q=:qSearchInternal search links
/?add-to-cart=:id/shopBasket linksOld basket links in emails
/wp-content/uploads/:fileThe same file on the new image host if it exists; otherwise 410 GoneMediaUnlinked, untrafficked images are allowed to go
/product/:slug/ not in the map410 GoneDiscontinued productsOnly the 140 agreed in Phase 1, with no traffic and no links. Never redirected to search or the home page, which search engines treat as a soft 404

Checks on every row: one hop only; the destination returns 200; the destination isn’t itself redirected; marketing parameters survive; the mapping makes sense to a person following an old link.

Have something to build?

Tell us what you're working on. We'll tell you how we'd approach it.

We reply within one working day.