Free AI Opportunity Audit

Drupal to Next.js Migration: What It Costs and What Breaks

Shyam Verma
Drupal to Next.js Migration: What It Costs and What Breaks

Short answer: a Drupal to Next.js migration runs 80 to 900+ engineering hours, or roughly $8,000 for a brochure site to $90,000 and up for a multilingual estate at $100/hour. Those are the same bands we publish for Joomla and WordPress, on purpose. What makes Drupal different is not the price. It is that the alternative — upgrading to Drupal 11 — is the same size of project, and almost nobody scoping this has been told that.

Drupal 7 reached end of life on 5 January 2025. Drupal 10 reaches end of life on 9 December 2026. If you own a Drupal site older than 11, one of those dates already applies to you — and if you are on 8 or 9, you went unsupported earlier still, in November 2021 and November 2023.

What does a Drupal to Next.js migration cost?

Hours are the real number. Dollars are those hours at $100/hour, which is a round figure chosen so the arithmetic stays visible, not a surveyed rate. Substitute whatever your market charges and the hour bands still hold.

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 nodes, custom content types 200–450 $20,000–45,000 8–16 weeks
Commerce or membership site with real accounts 450–900 $45,000–90,000 4–8 months
Multisite or multilingual estate 900+ $90,000+ 6–12 months

These are the same bands we publish for Joomla and WordPress. That is deliberate. The work is the same shape whichever PHP CMS you are leaving: model the content, move the content, rebuild the front end, preserve the URLs. Publishing three different tables for the same job would mean at least two of them were invented.

Estimates, not quotes. What moves a Drupal project up a band:

  • Contributed modules that produce front-end output. Each one is rebuild, replace with a service, or drop. Biggest driver, and countable before anyone quotes you.
  • Custom modules. Bespoke code. Nothing migrates it anywhere.
  • The theme. Twig templates, preprocess functions and a decade of template_preprocess_node() overrides do not travel. True whether you move to Next.js or to Drupal 11.
  • Views. Each view is a query, a display, filters, sorts and a rendered result. Simple ones are half a day. One with relationships, contextual filters and three displays is a week.
  • Accounts and commerce. Catalogue is content. Cart, checkout, tax and permissions are behaviour, and behaviour is the band above.

Calendar runs longer than engineering because migrations wait on people. Content review, redirect sign-off and client testing are weeks nobody bills.

We do not quote off a table like this either. We start with an audit, then a fixed-quote pilot — on a migration, one real section taken end to end, because the honest answer to "what will this cost" is usually "one section will tell us."

Your real choice is not migrate or stay

Every migration article assumes two options. Drupal has three, and people get the middle one wrong.

Drupal's own upgrade documentation says that going from Drupal 7 to Drupal 10 or later is not an update. It is a migration to a different site. The exact wording: "the content and configuration data from the Drupal 6/7 site must be migrated to a new Drupal 10 (or later) site." You stand up a fresh install, enable the Migrate and Migrate Drupal modules, point them at the old database, and run the content across.

The same page is blunt about what does not come with it. "Not all contributed modules have automatic upgrade paths. This may require a manual or custom migration." And on theming: "Drupal 10 (or later) significantly changed how themes are structured. These changes can't be migrated. Instead, a theme must be rebuilt to work in Drupal 10 (or later)."

Read that against the cost drivers above. New site. Content migrated by script. Contributed modules resolved one by one. Theme rebuilt from zero. Same work, same order, as leaving.

Extended support on 7 Rebuild on Drupal 11 Rebuild on Next.js
New site build required No Yes Yes
Content moved by script No Yes Yes
Theme rebuilt No Yes Yes
Contributed modules resolved individually No Yes Yes
Editors keep their admin UI Yes Yes No
PHP hosting after cutover Yes Yes No
Ends when Your vendor stops selling it Drupal 12, 13, 14

Two rows decide it. Editors keep their admin UI is the honest reason to rebuild on Drupal 11: if forty people know the Drupal editorial interface, that familiarity is worth real money and a new CMS spends it. Ends when is the honest reason not to. Drupal 8 went end of life in November 2021, Drupal 9 in November 2023, Drupal 10 goes on 9 December 2026, and Drupal 12 is scheduled for the week of 7 December 2026 — all of it on Drupal's published release schedule. Rebuilding on Drupal 11 buys a seat on that treadmill, which is fine if you want the seat.

Be fair to the seat, though. Drupal's upgrade documentation for version 8 and later describes nothing like the Drupal 7 job: update to the latest minor, get your contributed projects compatible, clear deprecated code out of your custom code and theme, then update the core codebase and run update.php. No new site, no scripted content migration, no theme rebuilt from zero. The rebuild is a one-time toll for leaving Drupal 7 — it is not charged again at 12, 13 and 14.

The third option gets dismissed too fast. The Drupal Association runs a vetted extended security support programme for Drupal 7, which buys time rather than solving anything. Twelve months to do the move properly is a legitimate use of money. A third year running is not a decision, it is a deferral.

I will not put a ratio on it. I have not run both paths on the same site, so I cannot tell you one is 30% cheaper. What the documentation establishes is that they are the same shape of project. Anyone quoting a Drupal 11 upgrade as a maintenance line item and a Next.js rebuild as a capital project is describing their own preference, not the work.

Getting the content out is the easy part

This is where Drupal is better than its reputation, and where it inverts the WordPress story.

JSON:API has been a stable module in Drupal core since 8.7.0. Once installed it serves from /jsonapi, and Drupal's documentation is explicit: "unlike the Drupal Core REST module, these paths are not configurable and are all enabled by default." Every entity type and bundle gets its own URL. No per-type opt-in, no annotation someone forgot five years ago, no field group sitting invisible to the API since the day it was created.

That is the exact opposite of show_in_rest, the thing that breaks coming out of WordPress, where a custom post type is absent from the API until somebody registers it correctly. Drupal's default is expose everything.

Three things people get wrong about it:

It is in core but not switched on. JSON:API is not in the standard install profile. Neither is core's REST module, nor Media. In core means shipped in the tarball, not running on your site.

Read-only mode is on by default. Drupal's security documentation states plainly that "read-only mode is turned on by default", configured at /admin/config/services/jsonapi. For a migration that is what you want. It matters the moment somebody proposes writing back through it.

It does not invent its own permissions. JSON:API "respects all of Drupal's security measures" — the same entity and field access the site already enforces. An anonymous request returns what an anonymous visitor could see, and unpublished nodes are absent unless you authenticate. Every "half the content is missing from the API" I have looked at was this.

For anything that is not entity data, you are on core's REST module, and Drupal's guidance is one sentence: "Choose REST if you have non-entity data you want to expose. In all other cases, choose JSON:API." Two cautions. REST needs each resource configured individually, with its own allowed formats, authentication providers and HTTP methods. And the usual trick for listing things, a View with a REST export display, has a sharp edge the docs name outright: REST export views "return all resources", with no pagination. On a 40,000-node site that is not an export, it is an outage.

None of this applies to Drupal 7, which never had a content API in core. There you read tables: node, field_data_field_* (Drupal 7 gives every field its own data table plus a field_revision_* twin), url_alias, file_managed, users. More work than calling an API, and entirely mechanical.

What actually breaks

Content survives. The structure around it is what costs money.

1. Paragraphs are a graph, not a field. Paragraphs is contributed, not core, and its project page reported about 245,000 sites using it when I checked in August 2026. Each paragraph is its own entity, referenced from the node through Entity Reference Revisions. A page with twelve paragraphs is thirteen entities and twelve revision-aware references, and nesting makes it a tree. You migrate a graph, then reimplement every paragraph type as a component. It is the most underestimated line on a modern Drupal migration, and also the good news: that graph maps onto typed components far more cleanly than a WYSIWYG blob ever does. On ReelAbilities we shipped 26 content blocks exactly this way, and a new page now takes 15 to 20 minutes with no developer.

2. Your file URIs are not URLs. Drupal 7 records uploads in file_managed with a uri column holding a stream wrapper, public://field/image/photo.jpg, not a web path. Any script treating that column as a URL produces a site full of broken images. Resolve the wrapper during import. And a Drupal 7 site has files, not media entities: Media only arrived in core in Drupal 8.4. If you are told "we will just migrate the media library", ask which library.

3. Aliases and system paths are two different things. Drupal's canonical path is /node/123. The pretty URL is a row in url_alias, where source is documented as "The Drupal path this alias is for; e.g. node/12" and alias as "The alias for this path; e.g. title-of-the-story". Google indexed the alias. Old emails, internal links and third-party integrations use the node path. Both have to keep answering. In Drupal 8.8 that storage moved out of url_alias into a path_alias entity, so exporting from a Drupal 9, 10 or 11 site means querying a different place than the tutorials from 2016 tell you to.

4. You may already have a redirect map, or none at all. Redirect and Pathauto are both contributed. Whether you own years of accumulated old-URL redirects depends entirely on whether somebody installed Redirect and switched on Pathauto's setting to generate one when an alias changes. If they did, that table is the most valuable thing in your database. If they did not, every URL that ever changed is already lost and only a Search Console export will find it.

5. Your SEO metadata is in a contributed module. Titles, descriptions and Open Graph tags come from Metatag, stored per entity and as inherited defaults built from tokens. Export the resolved values, not the token patterns, or you migrate [node:title] | [site:name] into a thousand meta tags.

6. Password portability depends on which Drupal you are on. Drupal changed password hashing in 10.1.0 to PHP's password_hash() and password_verify(), and new hashes carry the $2y$ bcrypt prefix. Older phpass hashes start with $S$ and are still accepted on a modern Drupal only because a Password Compatibility module gets installed automatically during the update. Each user is rehashed to bcrypt the first time they log in after that. So a site that moved from Drupal 7 to Drupal 10 last year still holds $S$ hashes for every user who has not signed in since. $2y$ ports to a Node auth layer that can verify bcrypt. $S$ does not. Count them before you promise nobody has to reset anything.

7. Views were doing more than you think. Views moved into Drupal 8 core, and on most sites it is not just listing pages. It is the homepage, the related-content block, the RSS feed, the sitemap source and three admin screens somebody built instead of a module. Each becomes a query and a component. Inventory them by counting displays, not views.

8. Revisions and moderation are a workflow, not a data structure. Drupal keeps every revision and, with Content Moderation, real editorial states, and teams build process on that. Decide early whether you are migrating revision history or drawing a line at the current published revision. Both are defensible. Deciding in month four is not.

Before anything else, run these two:

-- 1. Your real URL inventory (Drupal 7; use the path_alias table on 8.8+).
--    Diff this against a Search Console export. Anything Google knows that
--    this query does not return is a URL you are about to lose.
SELECT pid, source, alias, language FROM url_alias;

-- 2. How many users cannot bring their password.
--    Straight from Drupal's own change record for the 10.1 hashing switch.
--    Drupal 8+ as written; on Drupal 7 the table is `users`, not
--    `users_field_data`. Anything not starting with $2y$ is a forced reset.
SELECT uid, name, mail FROM users_field_data
WHERE pass IS NOT NULL AND pass NOT LIKE '$2y$%';

What Drupal does better than what you are moving to

I would rather you hear this from someone selling the migration.

Multilingual is in core and it is good. Four core modules since Drupal 8 (Language, Content Translation, Interface Translation, Configuration Translation) cover content, interface strings and configuration. That last one matters more than it sounds: your menus, views, block titles and field labels are configuration, and Drupal translates them through the same system as everything else. WordPress does this through WPML or Polylang, which is plugin-owned bookkeeping you then migrate. Joomla does it through association tables and a menu tree per language. If your site is genuinely multilingual, Drupal is the strongest of the three, and you should be honest about what you are giving up.

The entity and field model is better than what most teams build afterwards. Content types, fields, bundles, entity references, revisions and per-field access are one coherent system, refined across two major rewrites. Plenty of migrations end with a headless CMS reimplementing a chunk of it, badly. If your Drupal content model is well built, port it rather than throwing it away. The model is usually the part of a Drupal site worth the most, and Content Moderation on top of it is editorial workflow you get for free and rebuild at cost.

So the question is not whether Drupal is worse. It is whether you are paying for capability you use. A five-page brochure site running Drupal with Views, Paragraphs and a moderation workflow is paying enterprise CMS overhead to publish a contact page.

Who should stay on Drupal?

  • You are on Drupal 10 or 11 and nothing is broken. Minor updates are routine. Migration earns its cost when the platform blocks work you need, not because the stack is old.
  • Your content model is genuinely complex. Deep taxonomies, entity references in several directions, per-field access rules. Rebuilding that faithfully is expensive and Drupal already does it.
  • You are actually multilingual. Not "we might add French". Live locales, translated menus, translated configuration.
  • Your pain is one module. Replacing a module is a week. Replacing the platform is a quarter.
  • Nobody on your side writes React or runs a deploy pipeline. Next.js needs a build step and, beyond a static export, a Node runtime. A site you cannot maintain is worse than an old site you can.
  • You are inside a revenue-critical season. Migrate in your quiet quarter.

Move when the constraint is structural: the front end you need cannot come out of the theme layer, the contributed-module surface is a security problem you cannot patch fast enough, or you are already facing a rebuild-scale upgrade and would rather spend it on a destination you actually want.

Whichever way you go, do not do it in one cutover. Keep Drupal running and put the new site in front of it, section by section. That is the reverse-proxy approach, and it turns one terrifying launch into a sequence of small reversible ones.

I have not delivered a Drupal to Next.js migration for a client, and I will not imply otherwise. Our production migration work is WordPress to Next.js: four WordPress properties consolidated into one Next.js and Directus codebase for ReelAbilities, fifteen city festival sites running off it, with the engineering teardown including what went badly in a 12-month field report. What I bring to a Drupal job is the other half. Ready Bytes started in 2009 building extensions, on 10,000+ websites within two years, then a recurring-billing suite running on 40,000+ sites before we sold its IP in 2017. Those installs are the legacy sites now. I know what is in those tables because I spent ten years putting things in them.

Common questions

Is Drupal 7 still safe to run in 2026?
No. It reached end of life on 5 January 2025, and Drupal's own announcement is that "new releases of Drupal 7 core and contributed projects will no longer happen on Drupal.org and official community support is no longer provided." Vulnerabilities in core or in any contributed module you run stay unpatched unless you pay for extended support.

Is upgrading Drupal 7 to Drupal 11 cheaper than migrating to Next.js?
Not automatically, and that is the point most quotes miss. Drupal's documentation says a Drupal 7 to Drupal 10+ upgrade needs a new site, content migrated across, contributed modules resolved individually and the theme rebuilt. Same work as leaving. The saving is retraining, not engineering.

Can I keep Drupal as the backend and just replace the front end?
Yes. JSON:API has been in core since 8.7.0, exposes every entity type without configuration, and is read-only by default. It is a well-supported decoupled setup, and it also means patching Drupal and PHP forever. It solves your front end, not your maintenance burden.

Will I lose my Google rankings?
Expect them to move for a while whatever you do — Google documents temporary fluctuation for any site move, and it happens even when the work is done right. What keeping old URLs answering buys you is avoiding the permanent loss. Baseline Search Console before cutover, map both the aliases and the /node/123 paths, and judge at eight weeks rather than eight days.

Do my users have to reset their passwords?
Users whose hashes start with $2y$ can be carried to a bcrypt-verifying auth layer. Users still on $S$ phpass hashes cannot. Run the query above to find out which you have before you promise anything.

What is the smallest sensible first step?
Take one section end to end before committing to the site. That is what a fixed-quote pilot is for, and it is a reasonable thing to ask of any agency, including ours.


Shyam Verma founded Ready Bytes in 2009 and spent its first decade building products for the PHP CMS ecosystem. He writes about migrations, legacy modernization and applied AI at readybytes.in/blog. Been quoted for a Drupal upgrade or a replatform and want a second opinion? Send it over, including the quotes that are not ours.

Shyam Verma

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.

Comments