Every published chapter

The healthcare website migration playbook

5 chapters so far, in reading order. Each one answers a question, answer first. New chapters are added to this page as they are published.

  1. 1Should you migrate at all?
  2. 2What are you actually migrating?
  3. 3How do you model content for a multi-audience healthcare site?
  4. 4How do you migrate a healthcare site without losing its search traffic?
  5. 5What is a healthcare marketing site allowed to collect?

Chapter 1

Should you migrate at all?

Not necessarily. Score the site on eight areas, 1 to 5. Under 20 of 40, migrate. Over 30, fix what you have. In between, the low rows decide, not the total.

Not necessarily. Score your current site against eight things (editorial independence, content model, performance, accessibility, SEO resilience, compliance posture, integration health, operational risk) on a 1 to 5 scale. Under 20 out of 40, the platform is the problem and a migration is the cheaper fix over a three-year horizon. Over 30, keep what you have and fix the specific items. In between, it depends on which items are low, and I'll tell you how to read that below. A rebuild is expensive and disruptive; it should be the conclusion of an honest audit, not the starting assumption.

I sell migrations, so you should hold this chapter to a higher standard than the rest. The reason I can afford to be honest here is that a bad-fit migration is a bad project for me too.

What is the site actually failing at?

Usually one of two things: the team can't change it without a developer, or it broke something last time it was touched. Everything else is a symptom of those.

Before scoring anything, write down the three most recent times the site got in the way. Not "it's slow" in the abstract; the actual incidents. The new location page that took three weeks. The form that legal found out about. The redesign that lost a third of the search traffic. Those three stories tell you which rows below matter most, and they're what you'll use to explain the decision to whoever signs it off.

How do you score it?

Eight areas, each scored 1, 3, or 5, where 5 means there's nothing to do. Use the middle score when you're between descriptions. Be harsh; the score is for you.

  • Editorial independence. 1: a developer is needed for any change. 3: pages are editable, structure isn't; a new section or page type needs a ticket. 5: the marketing team ships pages and sections unaided.
  • Content model. 1: page-builder soup, every page a one-off, nothing reused. 3: some structured types (a provider or location type exists) but most pages are freeform. 5: structured and reusable; a provider or location entered once appears everywhere it should.
  • Performance. 1: Core Web Vitals red on mobile in field data. 3: mixed. 5: green field data and a budget that keeps it that way.
  • Accessibility. 1: serious WCAG failures on core templates (unlabelled forms, keyboard traps, no alternatives for media). 3: AA gaps on some templates. 5: AA, tested, with an accessibility statement.
  • SEO resilience. 1: no redirect discipline, thin or duplicate metadata, no idea what ranks. 3: the basics are present. 5: structured data, canonicals, and a process for redirects when things move.
  • Compliance posture. 1: health details in forms or analytics, no consent gate. 3: consent banner present, with gaps (tags fire before consent, free-text forms go to a marketing CRM). 5: PHI kept out by design, consent-aware tags.
  • Integration health. 1: undocumented and brittle; nobody knows what happens when the scheduling widget changes. 3: documented, some fragility. 5: documented, owned, monitored.
  • Operational risk. 1: a single vendor holds the keys, no backups, no runbook. 3: partial. 5: accounts in your name, backups tested, a runbook someone else could follow.

Add them up.

What does the total mean?

Under 20: migrate. Over 30: don't. Between: read the rows, not the total.

Under 20. Low scores across the board mean the platform and the way it was built are the cause, not any single feature. Remediating in place would cost a meaningful share of a rebuild and leave the structure untouched, so you'd be back here in two years. Plan the migration, and use the low rows as its requirements list.

Over 30. The site is in better shape than the rebuild conversation implies. Something specific is wrong, and the total tells you it's fixable in place. Fix the one or two low rows, keep the platform, and put the budget toward content or conversion instead. If a vendor pitches a rebuild at this score, ask them to show you which rows change.

20 to 30. This is most sites, and the rows that are low decide it. Low editorial independence and content model scores point to migration, because those are structural: a page-builder site doesn't become a structured one through remediation. Low performance, accessibility, or compliance scores with decent structure point to remediation, because those are fixable on any platform by someone competent. Low operational risk is urgent either way and shouldn't wait for a migration decision.

When is WordPress fine?

When the team can publish what they need to, the structure matches the content, and nobody is afraid to touch it.

WordPress runs a large share of healthcare sites and plenty of them are fine. A well-built WordPress site with a sensible custom-post-type structure, a small plugin footprint, good hosting, and someone who maintains it can score 30 or better on the rubric. If that's you, this playbook still has useful chapters (redirects, compliance, accessibility apply to any platform) but the migration chapters don't, and I'd rather you spent the money elsewhere.

The WordPress sites that score under 20 are usually not WordPress problems. They're page-builder problems, plugin-accumulation problems, and "the agency that built it is gone" problems. Migrating fixes those because it forces a rebuild of the structure, not because the new platform is magic. Be clear about which one you're buying.

What if you can't score it yourself?

Then that's the first finding: nobody on your side knows how the site works, and that's an operational risk score of 1 on its own.

You can get the score two ways. Have someone technical you trust spend a day with the rubric, the analytics account, and the CMS; that's enough for a rough but honest number. Or pay for an assessment, which is the same rubric done properly, with evidence for each score and a plan and a price at the end. I do those at a fixed fee, and the report is yours to use with any vendor, including your current one. Either way, get the number before anyone quotes you a rebuild.

Checklist

  • Write down the three most recent times the site got in the way
  • Score all eight areas, 1 / 3 / 5, harshly
  • Under 20: plan the migration; low rows are the requirements
  • Over 30: fix the specific rows in place; decline the rebuild pitch
  • 20 to 30: structural rows low means migrate; fixable rows low means remediate
  • Operational risk at 1: fix now, regardless of the migration decision
  • Can't score it: that's the finding; get someone to score it before anyone quotes a build

No starter code for this chapter. The rubric is the same one used in the Dock90 assessment.

Reviewed August 29, 2026 · This chapter on its own page

Chapter 2

What are you actually migrating?

Not the pages: the URLs, forms, scripts, integrations, redirects, linked files, and legal pages the old site accumulated. One spreadsheet; it's the contract.

# What are you actually migrating?

Not the pages. You're migrating a set of obligations the old site accumulated while nobody was looking: URLs that rank and are linked to, forms and wherever they send what people type, scripts and pixels and what they leak, integrations nobody documented, redirects already in place from the last migration, files linked from other sites, and legal pages with dates on them. Written down in one spreadsheet, that inventory is the contract for the whole project. It defines what done means, it prices the work, and it's what you check against on launch day.

The pages are the visible part. Most of what goes wrong in a migration was never a page.

Why isn't it the pages?

Because pages are the one thing everybody already knows to move, and because on the new site most of them stop being pages.

The failures I've seen or cleaned up after were not missing pages. They were a contact form that had been quietly posting to a marketing vendor for four years, and the vendor had never signed a business associate agreement. A remarketing pixel on condition pages that nobody on the marketing team knew was there. A patient intake PDF that three referring practices linked to from their own sites, gone the day the new site launched. A hundred and forty query-string URLs from an old newsletter tool that still got clicks. An appointment widget whose account was registered to an employee who left in 2021. None of those show up when you count pages.

The other reason is chapter 3: on a structured site, sixty to eighty percent of the old pages become records (providers, locations, services, FAQs) rather than pages. So "migrate 300 pages" is the wrong unit from the start. The right unit is the row in the inventory, and each row is a URL, a form, a script, an integration, or a file, with a fate.

What goes in the inventory?

Seven lists, joined where they share a URL. The first one is the biggest; the other six are where the surprises live.

  • URLs. A full crawl of the old site joined with twelve months of Search Console's Pages and Links reports, so every URL carries its traffic, its impressions, and its inbound links. Include the ones the CMS doesn't know about: attachment pages, tag archives, paginated feeds, author pages, campaign URLs with query strings. Chapter 4 covers how to build this list and turn it into the redirect map.
  • Forms. Every form on the site: the page it's on, where the submission goes (an inbox, a CRM, a vendor, a plugin's database table), the fields, whether any field is free text, which fields could carry health information, what notice or consent is shown, and whether the receiver is under a BAA. Submit each one with test data and follow where it lands. Plugin-stored submissions on the old host are a data set you'll need to export and then delete.
  • Tags and scripts. Export the tag manager container if there is one, then open a fresh browser profile with the network panel on and load one page of each template. Record every third-party request: vendor, what loaded it, which pages it fires on, whether it fires before any consent, and what it sends. This list is what chapter 5 is about; the inventory is where you find out how bad it is.
  • Integrations. Scheduling, CRM, email marketing, careers, reviews, chat, maps, payments, patient portal links, the analytics account itself. For each: what it does, who owns the account (the organization, a vendor, an ex-employee, unknown), who has admin access, and whether it's documented anywhere.
  • Existing redirects. The old site already has redirects, from a previous migration, a plugin, or the host's configuration. Export them. Any URL that already redirects needs its original address mapped straight to the final destination, not chained through the old hop.
  • Assets linked from outside. PDFs (patient forms, price transparency files, research posters), images that other sites embed, and anything a partner, a directory, or a hospital system links to. The Links report and a backlink tool find most of these; the crawl finds the rest. Each one is kept at the same path or redirected individually.
  • Legal pages. Privacy policy, terms, accessibility statement, notice of privacy practices, and any state-specific privacy disclosures, each with its effective date and whoever last approved it. These get re-reviewed for the new site because the new site collects different things.

An eighth list isn't content, but it belongs in the same spreadsheet: access. Who can log in to the registrar, DNS, the current host, the CMS, the analytics and Search Console properties, the tag manager, and each integration. Every migration I've run has stalled at least once on an account nobody could get into.

Who fills it in?

The crawl, the joins, the network-panel work, and the redirect export are technical and take a developer or an assessor a few days. The columns that need a human who knows the organization take the client weeks, and that is the part to start first.

The client's columns are: who owns this page or form or integration, whether it should survive, and what it should become. Those questions go to the marketing lead, the practice managers, the person who set up the CRM, and sometimes counsel. In a four-month build, this is the work that determines whether month one is productive or spent waiting. Of the three things clients reliably underestimate (content, approvals, and access; chapter 8 is about all three), two of them are exposed by the inventory in week one, which is the point of doing it in week one.

Why is this the contract?

Because every row gets a fate, and a row with a fate is a testable statement about the finished site.

Each URL is kept, merged into another, redirected, or retired. Each form is rebuilt with a named receiver, or removed. Each script is kept behind consent, replaced, or dropped. Each integration is carried over with a named owner, or cut. Each asset stays at its path or gets a redirect. Once the client has marked those columns up, the inventory does four jobs at once. It defines scope: anything not in it is a change, and a change has a cost. It prices the build: the counts by proposed type are the content model, and the number of forms and integrations is most of the integration work. It sets the launch gate: the parity check in the starter reads the old sitemap and refuses to pass until every address resolves on the new host, and the redirect import takes its rows straight from the map. And it's the record you compare against in the month after launch, when a 404 shows up in Search Console and you need to know whether it was ever supposed to exist.

A number from experience, offered as a range: a site the client describes as "about eighty pages" has between three and nine hundred URLs once the crawl and the Links report are joined. The gap between those two numbers is the migration.

What does the spreadsheet look like?

One workbook, one tab per list, and columns that force a decision. The content inventory is the core tab, one row per URL: template, content type, title, status code, indexable or not, word count, organic and total sessions for the last ninety days, conversions, external inbound links, last modified, owner on the client side, action (keep, merge, redirect, kill), target URL, proposed document type on the new site, priority, notes. The redirect map is generated from the rows where the action is merge or redirect, with columns for testing before launch, a day after, and two weeks after.

The forms tab, the tags tab, and the integrations tab carry the columns described above, each ending in a finding and a recommended action. The access tab is a checklist: item, what it's needed for, requested, granted, by whom, at what level. When I run an assessment, this workbook is half the deliverable and the client keeps it; it's the same workbook the build then runs on.

Keep the free text short. The spreadsheet carries facts and decisions; the reasoning goes in a document.

What can you skip?

Judging content quality, designing the content model, and anything with no traffic, no links, and no owner.

Content quality is a separate pass, after the inventory, and usually after launch; migration day is not the day to rewrite service pages. The content model is chapter 3, and it's designed from the inventory's counts, not alongside them; for now, assign each row a proposed type and move on. And a URL with zero sessions, zero impressions, and zero inbound links in twelve months gets marked kill and a 410, which is honest, rather than a redirect to the homepage, which is a soft 404 that costs crawl budget. Don't spend an hour deciding the fate of a 2019 holiday-hours post.

What you can't skip is the join. A crawl without Search Console data can't tell you which pages matter; Search Console without a crawl misses everything that isn't indexed. The inventory is the intersection, and doing only half of it is how a migration loses the pages nobody remembered were valuable.

Checklist

URLs and files

Everything that isn't a page

People and access - [ ] A client-side owner named for every row that needs a decision - [ ] Access checklist filled at kickoff: registrar, DNS, host, CMS, analytics, Search Console, tag manager, each integration - [ ] Accounts registered to former employees identified and transferred before the build depends on them Companion code in the Healthcare Sanity Starter: docs/MIGRATION.md ("Four weeks out: inventory" is this chapter's checklist in the repo), scripts/check-url-parity.ts (the launch gate that reads from the URL list).

Reviewed August 29, 2026 · This chapter on its own page

Chapter 3

How do you model content for a multi-audience healthcare site?

Ten document types, eight sections, audience as a field on pages, and providers, locations, and services as records entered once. That is the whole model.

# How do you model content for a multi-audience healthcare site?

Ten document types, eight section types, audience as a field on pages rather than a folder or a second site, and providers, locations, and services as structured records that are entered once and rendered everywhere they belong. That is the whole model for most clinic, practice, health-system, and life-sciences marketing sites. Anything a specific site needs beyond it is a one-type addition that takes twenty minutes, and the discipline is in what you leave out, not in what you add.

The model has one job: let the marketing team build and change pages without a developer while the site stays coherent as they do it. Every decision below is in service of that. If a modeling choice makes editors more capable but the site less consistent, or the other way round, it's the wrong choice.

Why is audience a field and not a folder?

Because the audiences share the same providers, locations, and services, and a folder (or a separate site) forces you to enter those twice and keep them in sync by hand.

A healthcare site serves patients, referring providers, partners, and, for life sciences, investors. Their pages differ; the facts behind the pages don't. A cardiologist has one bio, one set of locations, one accepting-patients status, and both the patient page and the referral page should read them from the same record. So in the starter, audience is a field on the page document: patients or providers, with a radio button in the Studio and patients preselected. That one value drives the navigation the page shows, the URL prefix (provider pages live under /for-providers), and how the Studio groups pages for editors. One dataset, one codebase, two front doors.

Two or three audiences is the right number to structure around. The others (careers, press, caregivers) get a page, not a section. I wrote about how to decide which audiences count in Patients, providers, partners; this chapter is about what the decision looks like in the schema.

Why ten types, and which ten?

Ten because that's the set that appears on almost every healthcare marketing site, and each one either has its own URL or is referenced from more than one place. That's the test for a type.

  • page. Title, slug, audience, a list of sections, SEO. The page-builder document; most of the site is these.
  • service. A service line, condition, or offering. Summary, body, FAQs, related providers, medical review. Has its own URL.
  • provider. Name, credentials, title, specialties, accepting patients, headshot, bio, locations. Has its own URL.
  • location. Name, address, coordinates, phone, structured hours, providers. Has its own URL.
  • post. Title, author, body, medical review, SEO. The blog.
  • person. Author or reviewer: name, credentials, role, bio. No URL of its own; rendered as bylines and in structured data.
  • faq. Question, answer, a category string for grouping. Referenced from services and from FAQ sections.
  • legalPage. Privacy policy, terms, accessibility statement, notice of privacy practices. Title, body, effective date.
  • redirect. From, to, permanent. Redirects are content, so editors can see and manage them (chapter 4).
  • settings. One document: organization name and logo, contact, social, default SEO, analytics id, consent copy, header and footer navigation per audience.

What is not on the list: insurers, jobs, press releases, events, research, clinical trials. Every one of those is real on some sites and absent on most, so they're add-ons. In the starter a new type is a schema file, a query, a route, and a line in three lists; the walkthrough in ADDING-A-TYPE.md does insurers end to end in about twenty minutes. The rule I hold to is that a type earns a place in the base model only after two real projects needed it. In your project, add what the client needs, and nothing the client might need.

Services or conditions?

One type for both, with the title and the URL doing the work.

Patients search for conditions and symptoms ("knee pain", "sleep apnea test"). Organizations think in service lines ("Orthopedics", "Sleep Medicine"). Modeling those as two types produces two trees that have to be cross-linked and kept in agreement, and within a year the condition pages are stale because nobody owns them. Modeling them as one service type means every entry gets the same fields (summary, body, FAQs, providers, review) and the same template, and the only decision per entry is what to call it. Name and slug it the way patients search; put the department name in the body.

If a site truly needs to group services, add a category string field first, the way faq has one. Add a separate taxonomy type only if the categories need their own pages, which is rare on a marketing site.

Why are providers and locations first-class?

Because they're the pages that rank for local search, they're the data behind Physician and MedicalClinic structured data, and they change more often than anything else on the site. A record entered once and referenced everywhere is the only way those three facts stay true at the same time.

A provider carries what a patient and a referrer both need: name without credentials, credentials separately, a title (Cardiologist, Nurse Practitioner), specialties as tags, whether they're accepting patients, a bio, and the locations they practice at. A location carries a structured address, coordinates, a display phone number (the dialable link is derived), and hours as days, opens, and closes rather than free text, because the openingHoursSpecification in the structured data needs them that way and a free-text "Mon–Fri 8–5" can't be turned into it reliably.

References go one direction: provider to locations. A location's provider list is derived from that unless an editor curates it explicitly. The same pattern applies to the page sections: a providers section with no references shows every provider (the directory); with references it shows a chosen subset. So when a physician moves to a different office, one document changes and the directory, the location page, the service pages that list them, and the structured data all follow. On a site where provider bios are copied into page bodies, that same move is a search-and-replace across the site and something always gets missed.

Where does medical review live?

As two fields, reviewedBy and reviewedAt, on the types that make clinical claims: service and post.

The reviewer is a person document, so a physician who reviews forty pages is one record with one set of credentials, not forty retyped bylines. The fields render as a byline on the page and go into the Article structured data, which is what search engines look at when deciding whether health content is trustworthy. The Studio requires a review date when a reviewer is set, and warns (rather than blocks) when a post has no reviewer, because some posts are operational (new parking, holiday hours) and don't need one.

The same two fields are the audit trail from chapter 5: when counsel or a regulator asks who signed off on a clinical claim, the answer is on the page.

What are sections, and why only eight?

Sections are the blocks an editor composes a page from: hero, rich text, call to action, cards, FAQs, providers, locations, form. Eight because each one either presents a structured type or holds prose, and once you go past that you're building a design tool, not a content model.

The distinction matters. A providers section doesn't contain providers; it references them, or shows them all. An FAQs section references faq documents. The section decides layout; the document holds the facts. That's what keeps a page-builder from decaying into page-builder soup, where every page is a one-off and nothing is reused.

The same principle limits the navigation. Header nav is capped at six links and one button per audience, enforced by validation in the Studio. A site that needs a mega-menu needs a different starting point, and it's better to find that out in week one than at launch.

What should stay out of the CMS?

Three things: anything a patient typed, anything that's configuration rather than content, and anything that lists everything.

Form submissions never touch the CMS. The CMS holds the form's definition (fields, which receiver it posts to); the submission goes to the receiver and nowhere else. The Studio warns when a field key looks like protected health information (date of birth, diagnosis, member id), which is a nudge for the editor and a tripwire for the reviewer. Chapter 5 covers why.

Secrets and integration configuration live in environment variables, not in settings. The analytics id and consent copy are content (marketing owns them); the webhook URL that receives forms is not.

Index pages (/providers, /locations, /services, /blog) are code, not page documents. They list everything and need no editorial input; making them editable only creates a way to accidentally hide half the providers. Those slugs are reserved so an editor can't create a page that collides with one.

How do you move existing content into this model?

Map, don't lift. Every URL in the inventory from chapter 2 gets assigned a type before anything is moved, and most old pages turn out not to be pages.

On a typical WordPress site, provider bios are pages or a plugin's custom post type; they become provider documents. Location pages become location documents. Condition and service pages, often forty or more, become service documents with the same template. FAQ blocks scattered across pages become faq documents that services reference. The blog becomes posts with an author and, where the content is clinical, a reviewer. What's left, usually the homepage, about, a handful of campaign pages, and the audience landing pages, becomes page documents built from sections.

In my experience, somewhere between sixty and eighty percent of the old page count collapses into structured records. That's the number to put in front of the client early, because it changes what "migrate the content" means: less copying, more deciding. Structured data, sitemaps, and internal linking then come from the model rather than from anyone remembering to add them.

Checklist

Model

Editing

Migration - [ ] Every inventoried URL assigned a type before any content moves - [ ] Provider and location records deduplicated: one document each - [ ] Structured data checked on one provider, one location, and one service page after import Companion code in the Healthcare Sanity Starter: sanity/schema/ (the ten types and eight sections), docs/ADDING-A-TYPE.md (adding a type end to end), docs/DECISIONS.md (the schema decisions and why).

Reviewed August 29, 2026 · This chapter on its own page

Chapter 4

How do you migrate a healthcare site without losing its search traffic?

Build an exact-match redirect map from a full inventory, keep everything but the URL the same, and don't launch until every old URL resolves on the new host.

You build an exact-match redirect map from a full inventory of the old site's URLs, you keep everything that isn't the URL the same (titles, canonicals, structured data, internal links, images), and you don't launch until an automated check proves every old address resolves on the new host. Do that and you'll see a dip of roughly 10 to 20 percent in impressions for a couple of weeks and then recovery. Skip any part of it and you'll see a cliff, and cliffs on healthcare sites are expensive: the pages that rank are the condition and service pages that bring in patients.

The mechanics are not complicated. They are tedious, and tedium is where shortcuts get taken.

Why do migrations lose rankings?

Almost always because URLs changed and the old ones were not mapped one-to-one to the new ones. Everything else on this page is a distant second.

Each URL carries history: how long it has existed, who links to it, how people behave when they land on it. When a URL disappears, that history goes with it unless a redirect says where it went. A 404 on a page with ten years of inbound links is a page that starts over from zero.

The second cause is a redirect that exists but points somewhere unhelpful, classically every old service page to the homepage. The engine follows it, finds a page about something else, and treats it as a soft 404. The third is quieter: the page moved correctly, but its title, structured data, or internal links changed at the same time, so the engine sees a different page at a new address. That doesn't cause a cliff. It causes a slow, unexplained slide over a month or two.

What goes in the redirect map?

Every URL on the old site that has had a visit or an inbound link in the last twelve months, mapped to exactly one destination.

Build it from the inventory, not from memory. In practice the inventory is three lists joined on URL:

  • A full crawl of the old site. Screaming Frog, Sitebulb, or npx sitemap-generator-cli for a small site. Don't trust the CMS sitemap alone; WordPress sites accumulate URLs nobody knows about (attachment pages, tag archives, paginated feeds, author pages).
  • Search Console's Pages report for the last twelve months. Anything with impressions has a place in the map.
  • Search Console's Links report and, if you have it, a backlink tool. Anything with an external link has a place in the map even if it gets no traffic.

Every row gets one of three fates:

  • Keep. Same path on the new site. The best outcome. Most provider, location, and service pages should be here if the new URL scheme is designed with the old one in mind.
  • Move. Redirect to the closest equivalent page. Closest means a page about the same thing, not a category page above it and never the homepage.
  • Retire. Return a 410 (Gone) or a plain 404. This is the honest answer for the 2019 holiday-hours post with zero traffic and zero links. Redirecting it to the homepage doesn't preserve anything; it just makes the engine work harder to figure out that the page is gone.

Two categories people forget:

  • Assets linked from outside. PDFs (patient forms, price transparency files, research posters), images that appear in other sites' articles, and anything a partner or directory linked to. Either keep them at the same path or redirect each one. A 404 on a PDF that a hospital system linked to is a real loss.
  • Query-string URLs. WordPress's ?p=123 style links and tracked campaign URLs. Path-based redirect systems, including the one in the starter, match paths only. Expand these from your analytics data into explicit rows.

The map itself is a CSV with three columns: from, to, permanent. A few hundred pages takes a day; several thousand takes a week and a spreadsheet the client's team helps mark up. That is why the inventory is the contract for the migration, not a side task.

Exact-match or wildcard redirects?

Exact-match. Always. If you have a pattern, expand it into rows.

Wildcard rules are how sites lose half their rankings. /services/* to /care/* looks correct until a third of the old service pages turn out to have been renamed, a dozen merged, and two retired; the rule sends all of them to URLs that 404 behind a redirect. An exact-match list is longer, but every row is verifiable: you can test it, hand it to the client and ask "is this right?", and diff it against the crawl to know it's complete. None of that is true of a regex.

The starter treats redirects as content: they're redirect documents in Sanity that editors can see and manage, compiled into the Next.js config at build time. The CSV is bulk-loaded with npm run import-redirects -- redirects.csv, which uses deterministic IDs so you can re-run it after fixing rows without creating duplicates, and a --dry-run flag that validates the file (malformed paths, loops, duplicates) without writing anything.

301, 308, or 410?

Use a permanent redirect for anything that moved, a 410 for anything retired, and don't overthink the difference between 301 and 308.

Both 301 and 308 mean "moved permanently"; 308 additionally preserves the request method, which matters for form posts and not for a page a search engine is fetching. Google treats both as permanent and passes ranking signals through either. Next.js emits 308 by default and that's what the starter ships. If a client's SEO agency insists on 301, it's not worth a fight, but it's also not worth an engineering change.

What does matter:

  • No temporary redirects for permanent moves. 302 and 307 say "this might come back", and engines keep the old URL indexed. If your host or CDN defaults to 302, change it.
  • No chains. Old URL to intermediate URL to final URL loses a little signal at each hop and slows every request. If a page moved in a previous migration and moves again now, point the original directly at the final destination. Crawl the old site's existing redirects and fold them into the map.
  • No loops. The dry-run catches these; a redirect that eventually points back at itself takes the page down entirely.
  • 410 over redirect-to-homepage for retired content. A 410 tells the engine to drop the page promptly. A redirect to the homepage tells it to spend crawl budget discovering that the page is a soft 404.

What besides the URL has to stay the same?

Everything the engine used to understand the page. Change the address and the contents at once and you've moved twice. For each kept or moved page, carry over:

  • Title and meta description. Rewrite them later, after the migration settles, as its own project. Migration day is not the day to improve titles.
  • Canonical URL. Pointed at the new page's own address, absolute, using the production domain. The starter derives canonicals, the sitemap, and structured data from one NEXT_PUBLIC_SITE_URL setting so they can't disagree with each other.
  • Structured data. Healthcare sites usually carry MedicalOrganization, Physician, MedicalClinic, and FAQPage markup, and those rich results are worth protecting. Compare the old and new JSON-LD on one provider page, one location page, and one FAQ page. Missing markup after launch is a common cause of the slow slide.
  • Internal links. Every link inside your own content that pointed at an old URL should point at the new one directly, not through the redirect. Content migration scripts should rewrite these; check a sample.
  • Image alt text and filenames. Image search is a meaningful traffic source for some healthcare content (conditions, procedures, facilities). Keep alt text, and keep image paths where you can.
  • Headings and body content. Same H1, same substantive content. Reorganizing and trimming is fine; replacing the page's subject is a new page.
  • Sitemap and robots. The new sitemap should list only final URLs (no redirecting ones) and robots.txt should not be carrying a staging-era Disallow: /. That last one has taken down more launches than any redirect mistake.

What does Search Console need before and after?

Before: a snapshot. After: the new sitemap, and your attention for a month.

Before launch, export from the old property: the Pages report (twelve months), the Links report, and the Core Web Vitals report. This is your baseline. Keep the old property; don't delete or merge it.

On launch day, verify the new domain as a property if the domain changed, submit the new sitemap, and use the URL Inspection tool to request indexing on the twenty or so most important pages. Don't request indexing on everything; it doesn't help and it's rate-limited.

In the first week, watch the Pages report for 404s and "Page with redirect" entries that don't match your map. Every unexpected 404 is a row you missed. Add it to the CSV, re-import, redeploy. This is why redirects live in the CMS in the starter rather than in code: fixing a missing row shouldn't need a developer.

Over the first month, compare impressions and clicks week over week against the baseline, per page for the top fifty and in aggregate for the rest.

What does a normal dip look like, and what's a cliff?

Normal is a 10 to 20 percent drop in impressions for one to two weeks, then recovery to baseline within four to six. A cliff is 40 percent or more in the first few days, or a dip that hasn't started recovering by week three.

Those ranges are from migrations I've run, not from a study. Where most pages kept their paths the dip can be close to nothing; where every URL moved, expect the deeper end.

A cliff has a cause, and it's nearly always one of three things: a missing block of redirects (usually a whole template or section), a noindex or robots disallow left over from staging, or canonicals pointing at the old domain or the staging domain. All three are findable in an afternoon with the Pages report and a crawl of the new site. Start there before anyone starts talking about "algorithm updates".

What's the launch gate?

An automated check that every URL in the old sitemap returns a 200 or a permanent redirect on the new host, with a zero exit code. Not a spot check, not a spreadsheet someone eyeballed. A script that fails the build.

The starter ships this as npm run check-url-parity -- --old https://www.oldsite.com --new https://preview.vercel.app. It reads the old site's sitemap, requests every path on the new host, and reports anything that isn't a 200, 301, or 308. Run it against a preview deployment two weeks before launch and keep fixing rows until it passes. Run it again on launch day against the live domain. Run it once more a week later, because someone will have "tidied up" something.

The gate changes the conversation. Without it, "are the redirects done?" is answered by whoever is most tired. With it, the answer is a number: 1,214 URLs checked, 0 failures.

Checklist

Four weeks out

Two weeks out

Launch day

After

Companion code in the Healthcare Sanity Starter: docs/MIGRATION.md (the full launch checklist), scripts/import-redirects.ts, scripts/check-url-parity.ts.

Reviewed August 29, 2026 · This chapter on its own page