The build is rarely what makes a migration slow.

Ask how long it takes and you get a number that assumes everything goes to plan. Then week three arrives, somebody is waiting on a DNS password, and the schedule you agreed has quietly moved by a fortnight.

Coding time is fairly predictable. Waiting time is where projects go over, and most of the waiting is on your side rather than the developer's.

What sets the clock

Four inputs decide the span, and only one of them is about writing code.

Template count sets the build length. Six distinct layouts take roughly twice as long as three, and page count barely enters into it once the templates exist.

URL count sets the routing length. Four hundred URLs means four hundred redirect decisions, most of them mechanical, some of them needing a human to look.

Then two things that have nothing to do with engineering. How fast you answer questions, and how fast you make content decisions. I have had two week projects run six weeks on those two alone.

The phases, week by week

This is a content site with a few hundred posts, which is the most common shape I see. Notice how much overlaps.

Week 1Week 2Week 3Week 4Week 5Audit and scopeTemplate buildContent migrationRedirects and metaPerformance passTesting and QALaunch and watch
A five week content site migration as I usually schedule it. The bars overlap because template work continues while content moves, and testing starts before the performance pass finishes. Weeks are fractional on purpose, since real phases do not begin on Monday morning.

Week one: audit and scope

Nothing visible happens and this is the week that decides the rest. Every indexed URL comes out of the sitemap, Search Console and the server logs. Templates get counted. Plugins get listed, and each one gets a decision: replace, drop, or keep in WordPress.

You get a written scope at the end of it. If a project is going to be wrong, this is the cheapest week to find out.

Weeks one to three: template build

Starts a few days into the audit, once the layout inventory is settled. One component per distinct template, wired to real content rather than placeholder text, because placeholder text hides every layout problem you are going to have.

Weeks two to three: content migration

Runs alongside the build. On a headless setup this is close to free, because the content stays in WordPress and Next.js reads it over the API. On a full export it is scripted, then spot checked by hand.

terminalbash
# Export everything, then check the file is actually complete# before anyone starts trusting itwp export --dir=./export --post_type=post,page # Count what came out and compare against the live sitegrep -c '<item>' ./export/*.xmlwp post list --post_status=publish --post_type=post,page --format=count

Those two numbers matching is the whole point of the check. When they do not match, find out why now rather than after launch.

Weeks three to four: redirects and metadata

The redirect map gets built and then tested, which are two separate jobs and people skip the second. A map nobody verified is a list of guesses.

scripts/check-redirects.mjsjavascript
import { readFileSync, writeFileSync } from 'node:fs'; // One "old,new" pair per line, straight out of the redirect mapconst rows = readFileSync('redirects.csv', 'utf8').trim().split('\n');const STAGING = 'https://staging.example.com';const failures = []; for (const row of rows) {  const [from, to] = row.split(',').map((s) => s.trim());   const res = await fetch(STAGING + from, { redirect: 'manual' });  const location = res.headers.get('location');   // Anything other than a single permanent hop to the right place is a problem  if (res.status !== 301 && res.status !== 308) {    failures.push({ from, issue: 'status ' + res.status });    continue;  }  if (location && new URL(location, STAGING).pathname !== to) {    failures.push({ from, issue: 'went to ' + location + ' not ' + to });    continue;  }   // Follow it and make sure the destination is not itself broken  const final = await fetch(STAGING + from);  if (!final.ok) failures.push({ from, issue: 'destination ' + final.status });} writeFileSync('redirect-failures.json', JSON.stringify(failures, null, 2));console.log(failures.length + ' problems in ' + rows.length + ' redirects');

Metadata moves across in the same window, because both jobs read from the same URL inventory and it is wasteful to build that twice.

Week four: performance and testing

Images, fonts, layout stability, render strategy per route. Then the real testing pass: redirects verified, metadata diffed against the live site, and a genuine look on a mid range Android rather than a desktop window resized to look like a phone.

Week five: launch and watch

DNS moves on a Tuesday or Wednesday morning. Then somebody watches Search Console for a fortnight, because crawl errors and coverage changes show up over days, not minutes.

Timelines by site size

SiteScaleSpanWhat dominates
BrochureUp to about 30 pages1 to 2 weeksTemplate build
Content siteRoughly 50 to 500 posts2 to 5 weeksRedirect map and content decisions
Headless content siteAny size, editors stay put3 to 6 weeksWebhook, preview mode, CMS lockdown
Membership or commerceAny sizeScoped separatelyAuth, payment and order history
Spans assume one engineer, decisions answered within a day or two, and no redesign running at the same time. Break any of those three and the range stops applying.

The last row is deliberately vague because quoting a timeline on commerce work without seeing it is how projects fail. Cart, checkout, payment and order history all have to be right on the first day.

What actually runs late

Five things, in the order I hit them most often.

  1. 01Content decisions. A hundred and fifty old posts with no traffic. Delete, redirect, or migrate? Nobody wants to make the call and the routing work waits.
  2. 02Access. Hosting logins, DNS, the analytics account, the domain registrar that someone's former agency still controls. Ask for all of it in week one.
  3. 03A plugin doing something undocumented. You find it in week three and it is quietly powering something a stakeholder cares about.
  4. 04Design creep. Once people see the new site they want changes. Every one is reasonable and they compound.
  5. 05Third party approval. Legal, brand, or an agency who owns the DNS and answers on Thursdays.

Only the third one is a technical surprise. The other four are scheduling problems, which means you can fix them in advance.

The delay you control

Do these four things and you take a genuine week or two off the span.

Hand over every credential in week one. Not when it is needed. Week one, in a password manager, all of it.

Name one decision maker. Someone who can say delete that page and mean it, without a meeting. This is the single biggest lever you have.

Do the content audit before the project starts. Pull your own analytics, mark what earns traffic, and decide what dies. If you arrive with that list, the routing work starts immediately instead of waiting on you.

terminalbash
# The pages that earned nothing in the last year are your delete candidates.# Export from Search Console, then find the URLs with no clicks at all.awk -F',' 'NR>1 && $2==0 {print $1}' search-console-pages.csv | sort > no-clicks.txtwc -l < no-clicks.txt

Then agree the design is frozen. Migrate the site you have. Redesign it as a separate project once the move has settled and you know the routing held.

What can run in parallel

Look at the chart again. Four of the seven phases overlap, which is why a five week calendar span is not five weeks of sequential work.

Template build and content migration overlap comfortably, since one is layout and the other is data. Testing starts before the performance pass ends, because you want the redirect checks running while somebody is still tuning images.

What cannot overlap:

  • The audit has to finish before routing starts. You cannot map URLs you have not inventoried.
  • Redirect testing has to happen on staging, before DNS. Testing after the switch is not testing, it is finding out.
  • The watching period comes after launch and cannot be compressed. Two weeks is two weeks.

One person cannot parallelise beyond a point, which is worth saying plainly. If you need three things finished on the same day, that is an argument for a studio rather than a faster individual.

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

Most of the price is time, so the two documents have to agree. A five week schedule against a one week budget means somebody has misread the job.

Related readingA WordPress to Next.js migration checklist you can hand to a developer

I run these end to end, and I will tell you in the first conversation whether your site is a two week job or a six week one. The migration page sets out the process, or tell me what you are running and I will give you a real schedule rather than a range.

Resources