Migrating from WordPress to Sanity: in stages, not a big bang

A sluggish backend, a plugin zoo, update anxiety: if that sounds like your WordPress site, you still don't have to risk a big bang relaunch. I'll walk you through the migration to Sanity in five stages: content audit, content model, export and NDJSON import, parallel operation behind Next.js, redirects. With official tooling, honest effort ranges from my projects, and the cases where WordPress gets to stay.
10 min readMatthias RadscheitMatthias Radscheit
Happycodingen-US

TL;DR

A sluggish backend, a plugin zoo, update anxiety: if that sounds like your WordPress site, you still don't have to risk a big bang relaunch. I'll walk you through the migration to Sanity in five stages: content audit, content model, export and NDJSON import, parallel operation behind Next.js, redirects. With official tooling, honest effort ranges from my projects, and the cases where WordPress gets to stay.

  • Stages instead of a big bang: the strangler pattern keeps the site online throughout, spares your editors a content freeze and makes every stage individually reversible.
  • The tooling route is officially documented: the WordPress REST API delivers rendered HTML, htmlToBlocks from @portabletext/block-tools (formerly @sanity/block-tools) translates it into Portable Text, and the Sanity CLI imports NDJSON (as of October 2026).
  • Stable document IDs derived from the WordPress post IDs plus the --replace option make the import repeatable at will: correct, re-import, done.
  • Images migrate themselves: the _sanityAsset property with the old image URL is enough, and the import pulls the media library into Sanity on its own.
  • Effort from happycoding projects: 20 to 40 person-days for sites with 50 to 300 pieces of content; the shortcode inventory drives the range more than the page count.

When the backend becomes a test of patience

Eight seconds until the editor loads. 34 plugins, and nobody can say anymore which ones the site actually needs. And before every update, the quiet question of whether everything will still be standing afterwards. If this sounds familiar: your WordPress site is not broken. It has grown over years, and the foundation no longer carries the load.

Why a WordPress backend turns sluggish over the years, and which quick fixes help, is something I took apart in WordPress backend too slow: what to do. This article is the sequel for the case where you want to fix the cause instead of the symptoms.

The good news: you don't need a relaunch with a cutover date, a night shift and clenched teeth. A migration to Sanity can be cut into five stages, with the site fully online at every point. That is exactly the path we'll walk through now: stage by stage, with the official tooling and honest effort ranges from my projects.

Why stages instead of a big bang

First, the definition: big bang means the new site is built out of sight for months and switched over completely on a single date. That sounds orderly, but it has three built-in breaking points.

  • Content freeze: During the build phase, your editors must not change anything substantial, or the old and new site drift apart. With a three-month project, that is three months of standstill.
  • SEO risk in one block: All URLs, templates and internal links change in a single night. If something goes wrong, you only notice once the rankings are already falling.
  • All-or-nothing budget: The value arrives only at the very end. If the project collapses before that, you have paid and received nothing.

The alternative is called the strangler pattern: a new frontend moves in front of both systems and takes over the site section by section until the old system is empty. Every stage delivers a usable result, and after each one you can pause. The rule of thumb: the migration is not the risk — the cutover date is.

Stage 1: the content audit decides what comes along

Grown WordPress sites carry ballast: orphaned landing pages from old campaigns, team pages maintained twice, blog posts from 2019 without a single visitor in the past year. None of it deserves migration effort. That is why every migration I run starts with an inventory, not with code.

Three sources are enough for the decision basis: the page list from WordPress, twelve months of traffic data from your web analytics, and the queries from Google Search Console. Out of that comes a table with three columns: migrate, archive, delete.

The inventory also includes the uncomfortable part: shortcodes and page builder elements. Write down every plugin that produces content, such as forms, sliders or tables. Every entry on this list needs its own translation rule in stage 3. From my audits: this list drives the import effort more than the raw page count.

One observation from my projects to close this out: on grown marketing sites, rarely more than two thirds of the content survives the audit. Nobody has missed the deleted rest yet.

Stage 2: content modeling in Sanity

Now for the thinking error I see most often: rebuilding the WordPress model in Sanity. A page with one big HTML field stays a page with one big HTML field, even with a Content Lake underneath. That throws away the actual reason for the move.

Sanity thinks in structured content: one document type per kind of content, so blog post, service page, team member, case study. Body copy becomes Portable Text, recurring elements become objects of their own. A benefits box with icon, title and text is then an object with three fields instead of an HTML desert.

The template for this comes from your audit in stage 1: which page types really exist, which building blocks repeat? On a typical marketing site, I end up with five to eight document types and about a dozen building blocks. Noticeably more is usually a sign that the inventory was incomplete.

When modeling, think of the fields WordPress carries along in the background: slug, meta title, meta description, publish date, author. They move into your document types as ordinary fields and come straight out of the REST API in stage 3. That preserves the SEO substance before a single line of frontend exists.

Invest the most care here: the content model is the only stage that is expensive to correct later. You throw an import script away after use; a crooked model stays with you for years.

Stage 3: export and import with official tooling

On to the hands-on core. The route from WordPress to Sanity has three steps: get the content out, translate HTML into Portable Text, import it as NDJSON. There is documented tooling for every step, and none of it is improvised (as of October 2026).

Step 1: get the content out of WordPress

Two routes are open. The classic WXR export under Tools → Export produces an XML file with the raw content. Its catch: shortcodes sit in the text unresolved. Your slider turns into a cryptic bracket line that no importer can do anything with.

That is why I almost always use the WordPress REST API: at /wp-json/wp/v2/posts, every installation serves its content as fully rendered HTML, up to 100 posts per request, pagination included. Shortcodes arrive already resolved. A Node script of two dozen lines collects the entire inventory this way.

One exception deserves a mention: builders like Elementor store their data past the content field in structures of their own. There you check case by case during the audit; sometimes rebuilding the page in the new content model is faster than any automated translation.

Step 2: translate HTML into Portable Text

For the translation, Sanity provides the htmlToBlocks function from the @portabletext/block-tools package; it used to be called @sanity/block-tools, and older guides still list it under the old name (as of October 2026). The function takes HTML and produces Portable Text blocks matching your schema. It translates headings, lists, links and emphasis on its own; for edge cases like inline styles or embedded videos, you add deserialization rules following the official guide.

This is where the shortcode list from stage 1 pays off: each entry becomes either a rule, a building block of its own in the content model, or a deliberate deletion. Plan this step iteratively: translate, review in the Studio, sharpen the rule, run it again.

Step 3: NDJSON import via the Sanity CLI

The target format is NDJSON: one file, one Sanity document per line. You import it with npx sanity datasets import -d production content.ndjson. The former standalone tool sanity-import was retired in March 2026; today the route goes through the regular Sanity CLI (as of October 2026).

Two details make the import repeatable. First: derive stable document IDs from the WordPress post IDs, such as post-123. Second: with the --replace option, every run overwrites the previous state. So you can correct and re-import as often as you like. The rule of thumb: an import you can only run once is not a tool, it's a bet.

The media library takes the same route: if you set the _sanityAsset property in the document with the old image URL, in the form image@https://your-site.com/wp-content/uploads/example.jpg, the import downloads the file on its own and creates it as a Sanity asset. Your images move house automatically, with no manual uploading.

Stage 4: parallel operation with the strangler pattern

Now comes the part that makes the big bang unnecessary. A Next.js frontend moves in front of both systems: it answers migrated routes from Sanity and passes all others through to the old WordPress via rewrites. From the outside, it stays one site under one domain, and nobody sees the construction site.

Technically, this takes little effort in Next.js: the rewrite configuration has a fallback mode that passes anything your new routes don't answer through to another address. The old WordPress keeps running on a subdomain for this, such as legacy.your-site.com, invisible to visitors.

The move then runs route by route. A realistic scenario for a marketing site with 200 pages: the blog first, because that is where most editorial work happens, then the service pages, and last the special cases such as the careers section and campaign landing pages. After each stage, you check traffic and rankings before the next one starts.

Your editors keep working the whole time: they maintain migrated sections in Sanity Studio, and the rest stays in WordPress for now. The dreaded content freeze disappears, because there is no period in which both systems have to hold the same content.

And if a stage goes wrong? Then you turn the rewrite back, and the old route is served by WordPress again. This reverse gear is the quiet value of the pattern: every step stays reversible, and no mistake is final.

Stage 5: redirects and SEO protection

Your rankings are capital, and this stage protects it. The most important decision comes early: keep URLs wherever possible. An article that lives on at /blog/my-article needs no redirect and keeps its signals without a haircut.

Where URLs do change, craft applies: permanent redirects, maintained as a mapping table from your audit and served through the redirects configuration in Next.js. One detail on that: with permanent: true, Next.js sends status code 308, which Google treats as a permanent move signal just like the classic 301. The signals transfer to the new address; expect weeks, not days.

After each stage move, three checks belong in your calendar: 404 errors in the log, the coverage report in Search Console, an updated XML sitemap. It sounds unspectacular, but it prevents exactly the ranking losses that make big bang relaunches so expensive.

What does it cost? Ranges from my projects

Now for the question behind every inquiry. The framing first: the following ranges are happycoding project experience, not an industry norm. They apply to marketing sites with 50 to 300 pieces of content; a wild shortcode inventory pushes them upward.

StageRange (person-days)
Content audit1 to 3
Content model and Studio setup3 to 8
Export and import scripts3 to 10
Frontend per page type1 to 3
Redirects and SEO protection1 to 2

In total, typical projects land at 20 to 40 person-days, spread over two to four months of parallel operation. The biggest uncertainty factor is the translation rules: a site with clean Gutenberg HTML ends up at the lower edge, a builder inventory with 15 content plugins at the upper.

The full truth includes the running costs of both worlds: what Sanity costs per month, and what the three-year total looks like next to WordPress with Next.js. The short version: the migration buys you lower operating costs, but only the years of use turn that into a return.

Checked honestly: when WordPress gets to stay

I earn money with Sanity projects, which is exactly why this section belongs here. In three situations I advise you against the migration:

  • The site is small and quiet: under 20 pages, rare changes, manageable backend pain. Then a migration is effort without a lever.
  • Plugins carry your business: booking system, membership area, a small shop. You would have to rebuild these functions, and that blows up any audit math.
  • Structured content gains you nothing: if one channel is enough and nobody reuses content, the modeling effort never pays back.

Are you torn between headless, a site builder and classic WordPress in the first place? Then settle that question before any migration. A middle path exists too: keep WordPress as the backend and render the frontend with Next.js — I've described that setup in headless WordPress with Next.js. And if traffic spikes are what drive you, compare how Sanity and WordPress behave under high load before you decide.

Next steps

If your site is a candidate: start with stage 1, and start this week. A content audit costs a few days, commits you to nothing and delivers value even if you end up staying with WordPress. How I set up and run Sanity projects is on my Sanity headless CMS service page.

Want to know up front whether the move pays off for your site? Send me the URL, and I'll tell you openly in a call what I would migrate and what I wouldn't: book a no-obligation intro call.

Frequently asked questions

How long does a migration from WordPress to Sanity take?
From my project experience: 20 to 40 person-days for marketing sites with 50 to 300 pieces of content, spread over two to four months of parallel operation. The biggest driver is not the page count but the inventory of shortcodes and page builder elements that need their own translation rules.
Will I lose my Google rankings during the migration?
Not if you stick to the craft: keep URLs where possible; for every change, permanent redirects (status code 301 or 308) maintained as a mapping table; after each move, check the 404 log, Search Console and the sitemap. The staged approach limits the risk further, because the whole site never moves at once.
Can I run WordPress and Sanity in parallel?
Yes, that is the core of the approach. A Next.js frontend answers migrated routes from Sanity and passes all others through to the old WordPress via rewrites. From the outside it stays one domain, your editors keep working throughout, and every stage is individually reversible.
What happens to the images in my WordPress media library?
They move automatically. In the NDJSON document, you set the old image URL as _sanityAsset, in the form image@https://your-site.com/path.jpg. The Sanity CLI downloads the file during the import and creates it as an asset; manual uploading is not needed.
Which tool converts WordPress HTML into Portable Text?
The htmlToBlocks function from the official @portabletext/block-tools package, the renamed successor of @sanity/block-tools. It translates standard HTML such as headings, lists and links on its own; for inline styles and special cases, you add your own deserialization rules, which Sanity documents in a dedicated guide (as of October 2026).
When is the migration not worth it?
For small, quiet sites under roughly 20 pages, when plugins such as a booking system or shop carry your business, or when structured content adds no value because one channel is enough. In those cases WordPress is the more economical choice, and I will tell you so.

Sources

Related articles

Open for select projects

Let's talk about your project

Book a no-obligation call, send us an email, or use the form – we'd love to hear from you.

150+
Completed projects
15
Years of experience
8
Senior‑level team members