Most people asking about this have already been quoted a number. The quote is somewhere between $8,000 and six figures, it came with no arithmetic attached, and there is no way to tell whether it is fair.
So here is the arithmetic. These are the bands we quote, the drivers that move a project between them, and the specific things that break when content comes out of WordPress — including the ones that turn a four-week job into a four-month one.
What does a WordPress to Next.js migration cost?
A WordPress to Next.js migration runs from about $8,000 for a brochure site to $90,000 and up for a multisite or multilingual estate. The number is driven by how much of your site is behaviour rather than content. Page count barely matters. Plugin count matters enormously.
| What you have | Engineering hours | At $100/hr | Calendar |
|---|---|---|---|
| Brochure site, under 50 pages, no logins | 80–160 | $8,000–16,000 | 3–6 weeks |
| Content site, 200–2,000 posts, custom fields | 200–450 | $20,000–45,000 | 8–16 weeks |
| WooCommerce store or membership site | 450–900 | $45,000–90,000 | 4–8 months |
| Multisite, or WPML/Polylang multilingual | 900+ | $90,000+ | 6–12 months |
The $100/hour is a round number for showing the derivation, not a rate card. Move it to whatever your market actually charges and the hour bands still hold.
These are the same bands we publish for Joomla migrations, and that is deliberate. The work is the same shape regardless of which PHP CMS you are leaving: model the content, move the content, rebuild the front end, preserve the URLs. Quoting different numbers for the two would mean one of them was invented.
What moves a project up a band:
- Active plugins that produce front-end output. Every one is a decision: rebuild it, replace it with a service, or drop it. Ten plugins is a conversation. Sixty is a project plan.
- Custom field groups. Each needs exposing on the WordPress side and a typed model on the Next.js side.
- A page builder. Elementor, Divi, WPBakery. Those pages get rebuilt by hand. Budget for it.
- Commerce. Catalogue is content and moves easily. Cart, checkout, tax and payment are behaviour and do not.
- Multilingual. Translations are separate posts joined by plugin-owned bookkeeping. That bookkeeping is what you are actually migrating.
For what it is worth, we do not quote a migration off a table like the one above either. Nothing narrows into a real number until someone has read your plugin list, your custom field groups and your URL inventory. That reading is the first piece of work on any migration, and it is worth buying on its own before anybody signs for a rebuild. The step after it is one real section taken end to end — because the answer to "what will this cost" is usually "less than either of us thinks, or considerably more," and one section is what tells you which. How we structure engagements.
Headless WordPress, or leave WordPress entirely?
This is the decision that sets the budget, and most migration content skips it. There are three end states, not two.
| Stay on WordPress | Headless WordPress | Leave entirely | |
|---|---|---|---|
| Editors use | wp-admin | wp-admin | A new CMS |
| Front end | PHP theme | Next.js | Next.js |
| Migration cost | Lowest | Middle | Highest |
| Systems to patch | One | Two | One |
| PHP hosting | Yes | Yes | No |
| Retraining | None | None | Yes |
The trap is in the "systems to patch" row. Headless WordPress does not remove the WordPress maintenance burden — it adds a Next.js application on top of it. You still run PHP, still patch plugins, still own that attack surface. You have only stopped showing it to the public.
That can be exactly the right trade. If your editorial team is fifty people who know wp-admin, the retraining cost of a new CMS is real and headless buys you a fast front end without paying it. But if you are migrating because of the maintenance burden, headless does not solve the problem you have. Be honest about which one you are.
What breaks when you pull content out of WordPress
The WordPress REST API has shipped in core since 4.7, which makes it tempting to assume the content is already portable. It mostly is. The exceptions are where the hours go, and they are not where people look.
Custom post types and taxonomies are invisible by default. A post type only gets a wp/v2 route if it was registered with show_in_rest set to true. Plenty of older themes and one-off plugins never bothered, because nothing was reading the API. Your "Case Studies" and "Team Members" can be sitting in the database and absent from the API at the same time.
Custom fields are opt-in too. Advanced Custom Fields values are not returned by the core API unless the field group is switched on for it, a setting ACF added in version 5.11. If ACF is holding the actual structure of your pages, that switch is the difference between a content model and a pile of titles.
Dynamic blocks put you in a bind. Gutenberg stores block markup in post_content with HTML comment delimiters. Static blocks keep their HTML there and travel fine. Dynamic blocks — query loops, latest posts, anything assembled at request time — typically save only the delimiter and their attributes, and their markup is produced by a PHP render_callback when the page is served. Some also save a static fallback that the callback replaces.
That leaves two options and no third one. Take content.rendered from the API and WordPress hands you rendered markup, including those dynamic blocks — but it is WordPress's markup, shaped for WordPress's stylesheets and scripts, which is most of what you were trying to leave behind. Or parse the blocks properly and map them onto your own components, in which case every dynamic block is logic you now have to reimplement, because the attributes are all you get. Neither is hard. Both are hours nobody counted, and this is the most common reason a migration that looked finished turns out not to be.
Page builders are worse, and not all in the same way. Elementor is the hard case: layout lives in postmeta as JSON, so content.rendered gives you nothing usable. Divi and WPBakery store shortcodes in post_content, and WordPress runs do_shortcode on the way out, so you do get markup — without the builder stylesheets that made it look like anything. All three ship an export. All three export back into themselves.
WooCommerce is two APIs, not one. The REST API you authenticate with a consumer key and secret is for managing the store — products, orders, customers. Cart and checkout are not in it. Those live in the separate, unauthenticated Store API at /wp-json/wc/store/v1. Catalogue migrates. Checkout is a rebuild, and it is why commerce sits a whole band above content sites in the table above.
Your users cannot bring their passwords. WordPress hashes them, so nobody can read them — not you, not the plugin, not us. Core switched its user password hashing from phpass to bcrypt in 6.8, but that changes nothing about portability: a hash is still a hash. Either you keep WordPress as the identity provider, or every user resets on cutover. Decide before launch, because "everyone resets their password" is a customer-communications project, not a deployment step.
Translations are bookkeeping, not content. WPML and Polylang both store each translation as its own post and join them with plugin-owned records. Which plugin you have — and which edition — decides whether that join is cheap or a project. Polylang Pro adds lang and translations onto the standard wp/v2 response; free Polylang does not, and free Polylang is what most sites are running. WPML exposes its own namespaces rather than annotating wp/v2, so linking a post to its translation still takes custom work. Losing the join is how a multilingual migration ends up with the right number of pages and no idea which is which.
SEO metadata travels differently depending on which plugin you run. Yoast appends yoast_head and yoast_head_json to wp/v2 responses by default, so on a Yoast site the titles and descriptions are already there for the taking. Rank Math is the opposite: you switch on its headless support and then query a separate rankmath/v1/getHead endpoint. Either way, carrying the metadata across is a deliberate step somebody has to schedule, and skipping it is a self-inflicted ranking drop on launch day.
How do you keep your Google rankings through a migration?
This is the risk that matters. Everything else is money; this one is revenue.
Expect some wobble regardless. Google says so in its own site-move guide: with any significant change you may see ranking fluctuations while it recrawls and reindexes, and visibility "may fluctuate temporarily during the move." What keeping old URLs answering buys you is avoiding the permanent loss. Take a Search Console baseline before cutover and judge at eight weeks, not eight days.
Keeping them answering needs three tools, and most migrations only reach for the first.
One: redirect the URLs that have a real equivalent. In Next.js these go in redirects() in next.config.ts with permanent: true, which emits a 308 rather than the 301 you may be expecting — Next.js chose 307/308 because they preserve the request method, and Google treats 308 as a permanent redirect just as it does 301. Be aware there is a ceiling. Hosting platforms cap how many entries a redirect table can hold, and a large legacy site will find that cap, so past a few hundred rules the advice is to stop enumerating and handle the rest in Proxy. (Proxy is what Next.js used to call Middleware — the convention was renamed, and a lot of older migration advice still uses the old word.)
Two: match URL shapes, not URLs. WordPress generates whole families of URLs from patterns — /category/<term>/page/2/, dated archives, ?p=123 short links, /feed/. Enumerating those one by one is how you blow through a redirect table. Handle them as patterns in Proxy instead and a dozen rules replace a few thousand lines.
Three: use 410 for what is genuinely gone. Not every old URL deserves a destination. Bulk-redirecting dead pages to your homepage is the standard shortcut and it buys you nothing. Google's site-move guide says it plainly: don't redirect many old URLs to one irrelevant destination such as the new homepage, because it confuses users and "might be treated as a soft 404 error." That is the trap, because soft 404s keep getting crawled and keep spending your crawl budget. A 410 — or a plain 404, Google treats all 4xx the same here — ends the argument: the page is gone, crawlers stop retrying it, and nothing pretends otherwise.
This site runs all three. There are 112 permanent redirect rules in next.config.ts for URLs with real destinations, pattern rules in src/proxy.ts for the legacy URL shapes its old CMS produced, and a short list of 410 patterns for pages that are simply gone. Ordering matters and it is not obvious: redirects() from next.config.ts runs before Proxy, so specific redirects win and only unmatched URLs fall through to the 410s. Get that backwards and you 410 pages you meant to keep.
We know the numbers because it is our own site, migrated off a PHP CMS, and the rules are in the repository rather than in a slide.
Who should stay on WordPress?
Plenty of sites should not move. Where WordPress differs from Joomla is what drives the bill: on WordPress it is the plugin ecosystem you have accumulated, and the more of it that produces front-end behaviour, the worse the arithmetic gets.
Stay if:
- Your site is mostly content and your editors are happy. A fast host, a caching layer and a plugin diet will get you most of the performance for a fraction of the cost.
- You depend on plugins you would have to rebuild. Booking systems, LMS platforms, complex WooCommerce extensions. Rebuilding a plugin you currently rent for $99 a year is rarely the cheaper path.
- Nobody on your team writes React. A Next.js site you cannot maintain is worse than a WordPress site you can. Either budget for the retainer or do not start.
- Your real complaint is hosting. "The site is slow" and "the CMS is wrong" are different diagnoses. Measure before you migrate.
Move when the constraint is structural rather than cosmetic: you need one codebase serving many sites, the plugin surface has become a security problem you cannot patch fast enough, or you need a front end that the CMS's template layer genuinely cannot produce.
For what that looks like when it goes well: we consolidated four WordPress properties into a single Next.js and Directus platform now running fifteen city festival sites for ReelAbilities, and wrote up the engineering side — including the parts that went badly — in a 12-month field report. If you need the old site live while the new one is built, the reverse-proxy cutover is how we do that.
Common questions
How long does a WordPress migration take?
Three to six weeks for a brochure site, eight to sixteen for a content site, four to eight months for commerce. The calendar is usually set by content decisions and approvals, not by engineering.
Can I keep wp-admin and just replace the front end?
Yes — that is headless WordPress, and it is a legitimate choice when retraining editors would cost more than running a second system. It does not reduce your patching burden.
Will I lose my Google rankings?
Expect temporary fluctuation — Google documents that for any site move, and it happens even when the work is done right. What you avoid by keeping old URLs answering is the permanent loss. Budget properly for redirects, patterns and 410s, hold a Search Console baseline before cutover, and judge at eight weeks rather than eight days.
What happens to my plugins?
Each one is rebuilt, replaced with a service, or dropped. Counting them is the fastest way to sanity-check any quote you have been given — including ours.
Do my users have to reset their passwords?
Unless you keep WordPress as the identity provider, yes. Password hashes are not portable by design.
What is the smallest sensible first step?
Migrate one real section end to end before committing to the whole site. That is what a fixed-quote pilot is for, and asking for one is a reasonable thing to do with any agency, not just us. If you are still shortlisting, we compared seven legacy modernization agencies — six competitors and ourselves — on scope, pricing transparency and published proof.
Shyam Verma is the founder of Ready Bytes, which has been building and migrating content platforms since 2009. If you want a second opinion on a quote you have been given, send it over — including the ones that are not ours.

Shyam Verma
Full Stack Developer & Founder
Shyam Verma is a seasoned full stack developer and the founder of Ready Bytes Software Labs. With over 13 years of experience in software development, he specializes in building scalable web applications using modern technologies like React, Next.js, Node.js, and cloud platforms. His passion for technology extends beyond coding—he's committed to sharing knowledge through blog posts, mentoring junior developers, and contributing to open-source projects.



