Every published chapter
The healthcare website migration playbook
8 chapters so far, in reading order. Each one answers a question, answer first. New chapters are added to this page as they are published.
- 1Should you migrate at all?
- 2What are you actually migrating?
- 3How do you model content for a multi-audience healthcare site?
- 4How do you migrate a healthcare site without losing its search traffic?
- 5What is a healthcare marketing site allowed to collect?
- 6What does accessibility require on a healthcare site?
- 8What does the migration itself look like?
- 9What happens in launch week and the month after?
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
Chapter 5
What is a healthcare marketing site allowed to collect?
Contact details, with consent, sent somewhere you control. Not health details. Make it true by design: webhook-only forms, consent-gated tags, typed events.
Contact details, with consent, sent somewhere you control. Not health details. The rule I build to is that a marketing site should hold no protected health information at all, and the way to make that true is architecture, not policy: forms post to a receiver and never store anything, analytics loads only after consent and can only send events you've defined in advance, and nothing else loads from anyone else. A site that holds no PHI has nothing to breach, and that is a much stronger position than a site that holds a little and tries to protect it.
This chapter is what I'd tell a marketing lead before their first legal review. It is not legal advice; it's the engineering side of the conversation, so the legal side goes faster.
Why should a marketing site hold no PHI?
Because the moment it does, HIPAA applies to the site, and a marketing stack was never designed to meet it.
Protected health information is health information that can be tied to a person. On a marketing site, that's usually created by accident: someone fills in "reason for visit" on a contact form, the form emails the submission to a shared inbox, the inbox is hosted by a vendor with no business associate agreement, and now a patient's name and condition sit in three systems nobody vetted. None of that was a decision. It was a default.
The alternative is to decide, up front, that the site is a brochure with a doorbell. It tells patients what you do and how to reach you, and the reaching happens somewhere built for it: a scheduling vendor under a BAA, a phone line, a portal. Once that's the design, the compliance conversation about the website gets short, and the long conversation moves to the systems that should be having it.
Where do form submissions actually go?
To a webhook receiver you configure, and nowhere else. The receiver is the real compliance boundary, not the form.
In the starter, the form section posts to a single configured URL. The server action forwards the submission and never logs the request body, nothing is written to Sanity or to a database, and there's no submissions inbox to secure because there are no submissions stored. The site is a pass-through.
That moves the question to the receiver, which is where it belongs. Whatever sits at that URL is where the data lives: a CRM, an email service, an automation tool, your own endpoint. Ask two things about it:
- Could a patient ever type something clinical into this form? If there's a free-text field, the answer is yes, whatever the label says.
- Is the receiver allowed to see that? Meaning: is there a BAA, and is the vendor operating as a covered system?
If the answers are "yes" and "no", you have three choices, in order of preference. Remove the free-text field. Route the form to a receiver that does have a BAA. Or keep the form as contact-details-only and send anything clinical to the phone or the portal, which is what the starter's seeded form copy does in plain words: don't include medical details here.
What counts as PHI in a contact form?
Less than people fear and more than they want. The test is whether a health fact can be tied to a person, and a contact form ties everything to a person by design.
Name, email, and phone on their own are not PHI. They're contact details, and every business collects them. The health part enters through fields like these:
- A free-text "message" or "reason for visit" field. Anything a patient writes there could be clinical.
- A dropdown of services or conditions on a form that also collects a name. Choosing "oncology second opinion" next to your name is a health fact tied to a person.
- Date of birth, insurance ID, member number, medical record number. These exist to identify patients within clinical systems and have no business on a marketing form.
- The page the form sits on. A generic "contact us" form submitted from /conditions/hiv-care carries the condition with it if the page URL is included in the submission, and most form tools include it.
The starter warns editors in the Studio when a form field key looks like one of these (dob, ssn, mrn, diagnosis, condition, medication, insuranceId), with a note explaining why. It's a guard rail, not a guarantee: it looks at what the field is called, not at what a patient types. That's why the copy matters as much as the schema.
Why do analytics tags need consent on a health site?
Because a pixel on a page about a condition can send what amounts to a health fact about the visitor to a third party, and regulators have gone after health companies for exactly that.
The mechanism is mundane. A visitor reads a page about a specific condition. The advertising pixel on that page sends the page URL, and often an identifier that ties the visit to an account on the advertising platform, to the vendor. Nobody typed anything. The URL did the work. HHS published guidance on tracking technologies on health websites in 2022 and revised it in 2024; the FTC has brought enforcement actions against health companies for sharing browsing data through pixels and chat tools. Several state privacy laws now define "consumer health data" broadly enough to include inferences from browsing, Washington's My Health My Data Act being the most-cited.
The engineering answer is a consent gate, and it has to be real:
- Nothing loads before the visitor agrees. Not "loads but doesn't send". The script isn't on the page. In the starter, GA4 is the only analytics tag and it loads only after analytics consent; Consent Mode v2 signals are sent so Google honours the choice on its side too.
- The gate can't be bypassed by a marketer. On most sites the tag manager is where compliance dies: someone adds a pixel for a campaign and it fires on every page, unconditionally. The starter ships no tag manager and no third-party scripts beyond GA4 and Turnstile on form pages. Adding one is a code change that goes through review, which is the point.
- The consent tool itself sets no cookie and calls no vendor. Consent is stored in the browser's local storage. The tool that asks permission to track shouldn't track.
Check it after launch, from a fresh browser profile, with the network tab open. If anything from a third party loads before you click accept, the gate isn't real.
How do you keep names and emails out of analytics?
Make it impossible to send an arbitrary string.
The usual way PII reaches analytics is an event like track('form_submit', { email: user.email }), written by someone who wanted a conversion count and didn't think about the parameter. In the starter, track() accepts a closed set of event names and typed properties for each. There's no free-form event and no free-form string property, so the mistake can't be made without changing the type definitions, which is again a reviewed change. It's a small amount of code and it removes an entire category of incident.
What about chat widgets, session replay, maps, and scheduling embeds?
Each one is a third party on the page, and each needs the same two questions as the form receiver: what can it see, and is it allowed to?
- Chat widgets see whatever the visitor types, which on a health site includes symptoms. Several of the enforcement actions above involved chat tools. If you need chat, it needs a BAA and it should load after consent, on the pages where it's needed, not everywhere.
- Session replay records the screen, including form fields. On a health site that's a recording of patients typing. I don't put it on healthcare marketing sites.
- Map embeds and scheduling widgets are usually fine, but they load third-party scripts and often set cookies. Confirm what they set, gate them if they track, and confirm the scheduling vendor's BAA since that's where the clinical conversation goes.
The default in the starter is that none of these exist until someone adds them on purpose.
When do you need a BAA?
When a vendor will handle PHI on your behalf. Not for the marketing site itself, if it's built the way this chapter describes, and yes for the systems the site hands off to.
A business associate agreement is a contract that makes a vendor responsible for protecting PHI they handle for a covered entity. A repository can't sign one and a website builder can't make you compliant. What the design does is keep the marketing site out of scope so the BAA conversation is about the right systems:
- Needs one: the scheduling vendor, the portal, any form receiver that could see clinical text, a chat tool that talks to patients, an email service that sends anything clinical.
- Doesn't, if the site holds no PHI: the hosting platform, the CMS, the analytics tool, the bot-protection service. These see marketing content and consented, typed events.
One caveat on the CMS. Sanity stores marketing content. If an editor pastes a patient's story into a post, that's PHI in the CMS, and no architecture prevents it. Train editors, and if your policies require it, Sanity offers HIPAA-eligible plans.
What should legal review before launch?
A short list, if the design above is in place. A long one if it isn't.
- The privacy policy describes what the site actually does: which analytics, gated how, what the forms collect, where submissions go. The starter seeds the privacy policy, terms, accessibility statement, and Notice of Privacy Practices with realistic placeholder text and a "replace before launch" banner; counsel replaces them for your jurisdiction and your state's privacy law.
- Every form's receiver, and whether it's allowed to see what the form could collect.
- Every third-party script, with the page it loads on and whether it loads before consent.
- Who reviews clinical content, and whether that's reflected on the page. The starter carries reviewedBy and reviewedAt on services and posts, rendered as bylines and in structured data. That's for search engines, and it's also how you show a regulator that clinical claims were reviewed.
Checklist
Design
Before launch
After
Companion code in the Healthcare Sanity Starter: docs/COMPLIANCE.md (this chapter's mechanics, in the repo), lib/consent.ts, lib/track.ts.
Reviewed August 29, 2026 · This chapter on its own page
Chapter 6
What does accessibility require on a healthcare site?
WCAG 2.2 AA, checked by axe on every change and by hand on the pages patients use, plus an honest accessibility statement. Healthcare is the most-sued sector.
# What does accessibility require on a healthcare site?
WCAG 2.2 at level AA, checked automatically on every change and by hand on the pages patients actually use, with an accessibility statement that says so and gives people a way to tell you when something fails. That is the whole requirement. It is not a feature for a later phase, because healthcare is the sector where web accessibility suits are filed most often, and because a patient who can't get through your appointment form with a screen reader has been turned away at the door.
This chapter is the other half of the compliance conversation. Chapter 5 covered what the site is allowed to collect; this one covers who is able to use it.
Why is the bar higher for a healthcare site?
Because the law treats a clinic's website as part of the clinic, and because the people most likely to need an accessible page are the people most likely to be looking for care.
The Americans with Disabilities Act names healthcare providers as public accommodations, and courts have treated their websites as covered for years. Web accessibility suits in the US run to thousands a year, and healthcare is consistently among the sectors named most, partly because the suits are cheap to file and a site is easy to test from a plaintiff's laptop. Two 2024 rules turned the informal standard into a written one: the Department of Justice set WCAG 2.1 AA for state and local government sites, and the Department of Health and Human Services set the same level under Section 504 for organisations that receive HHS funding, which includes most providers that bill Medicare or Medicaid, with compliance dates in 2026 and 2027. Ask counsel which of these applies to you. Build to 2.2 AA and the answer doesn't change what you build.
The practical reason is simpler. Patients with low vision, patients with a tremor, older patients, a parent on a phone with one free hand: the same page has to work for all of them. Accessible design and design for a visitor in a hurry turn out to be the same design.
What does WCAG 2.2 AA actually ask for?
Eight things, on a marketing site. The full guideline is longer; this is the part that applies to pages, forms, and images.
- Contrast. Text at 4.5:1 against its background, 3:1 for large text and for controls. The starter's design tokens carry the ratio for every colour pair, and links use a darker accent than buttons so that accent-as-text reaches 10.5:1.
- Keyboard. Everything the mouse can do, the keyboard can do, and the focused element is always visible. One focus ring, three pixels, on everything, everywhere. No element traps focus.
- Images. Every image has alternative text or is marked decorative. In the starter, alt is a required field in the schema and a required prop on the image component, so an image without it can't be published or rendered.
- Headings. One H1, then levels in order, describing the content and not the styling. The starter's heading component takes a level and enforces it.
- Forms. Labels tied to their fields, instructions before the field, and errors that name the field and say what to do, in text, announced to assistive technology.
- Targets. WCAG 2.2 added a minimum of 24 pixels for anything you tap. The starter uses 44, which is what fingers actually need.
- Motion and zoom. Animation stops when the operating system's reduced-motion setting is on. Text scales to 200 percent without anything overlapping or disappearing.
- Structure. A skip link as the first focusable element, page landmarks, and a page title that says which page it is.
None of these is hard on a new build. All of them are hard to retrofit into a WordPress theme that somebody else wrote, which is why accessibility is one of the eight lines in the migrate-or-remediate rubric in chapter 1.
What does an automated check catch, and what doesn't it?
Roughly a third of the criteria, reliably, and none of the ones that need a person to judge meaning.
axe, the checker the starter runs in CI, is good at contrast, missing labels, missing alt attributes, ARIA used wrongly, heading order, duplicate ids, empty links and buttons, and a page with no declared language. It finds these every time, in seconds, on every change.
It does not find alt text that exists but is useless ("image.jpg", "doctor"). It does not notice a focus order that jumps from the header to the footer and back. It sees a consent dialog; it does not try to dismiss it from the keyboard. It can't tell that a chart's meaning is carried by colour alone, that the reading order of a two-column layout is wrong, that a video has no captions, that an error message says "invalid" with no instruction, or that the PDF linked from the services page is a scanned image. Those are the failures patients actually hit.
So the automated check is the floor. It stops the site regressing on the things it can see. It does not tell you the site is accessible.
Which manual checks matter?
Five, about an hour, on four pages: the home page, a provider page, a location page, and the contact form.
- Unplug the mouse. Tab through the navigation, open and close the mobile menu, dismiss the consent dialog, submit the form. Focus is visible at every step and goes where you'd expect. If you ever lose track of where focus is, that's a failure.
- Read one provider page with a screen reader. VoiceOver is built into every Mac; NVDA is free on Windows. Does the page make sense read aloud? Can you jump between headings? Does the photo's alt text say something a patient would want to know? Does the booking link say where it goes, or just "click here"?
- Break the form on purpose. Submit it empty, then with a malformed email. The error is announced, names the field, tells you what to fix, and focus moves to it.
- Zoom to 200 percent, then turn on reduced motion. Nothing overlaps, nothing is cut off, nothing still animates.
- Turn off styles, or open reader mode. The order of the content still makes sense without the layout.
Write down what you find. That list is the "known limitations" section of the statement, and it's the backlog.
How do you keep it accessible after launch?
By making the checks part of every change and the guardrails part of the content model, because a site is accessible on launch day and drifts from there.
The starter runs a Playwright smoke test with axe on the home page, a provider page, and the contact form on every pull request, and fails the build on any violation against WCAG 2.0, 2.1, and 2.2 at A and AA. Lighthouse runs alongside it with an accessibility score under 95 treated as an error, not a warning. Neither takes a developer's attention; they run on the change and block it if it regresses.
The larger risk after launch is editorial, not technical. Images pasted without alt text, headings chosen for their size rather than their level, a PDF uploaded where a page should be. The content model is where that gets caught: alt required on every image, a heading component that only offers the right levels, no raw HTML field, and a line in the editor's guide that says why. Chapter 3 covers the model; this is one of the reasons it's shaped the way it is.
Then the statement gets reviewed once a year and whenever the site changes shape. Its known-limitations section should get shorter.
What goes in the accessibility statement?
Five things, in plain language, on a dated page in the CMS where counsel can edit it.
- The standard. WCAG 2.2 at level AA, named.
- How it's checked. Automated checks on every change, manual checks on the pages that matter.
- Known limitations, honestly. Older PDFs is the usual one. Say what they are, what you're doing about them, and how to get the content another way.
- How to tell you. A phone number and an email address that a person reads, and a response time you'll actually meet. Three business days is reasonable.
- The clinics themselves. Step-free access, accessible restrooms, interpreter services including American Sign Language. The web page is where patients look for that before they visit.
The starter seeds a statement with this shape as one of its four legal pages, with a banner that says to replace it before launch.
Two things the statement is not. It is not a badge from an overlay vendor: the widgets that add an accessibility menu to a page do not fix the underlying markup, they're now named in a growing share of the suits, and axe flags some of them. And it is not a promise of perfection. An honest statement with a working contact does more for you in a complaint than either.
Checklist
Build
Check
Publish
Companion code in the Healthcare Sanity Starter: e2e/smoke.spec.ts (the axe check that runs on every change), lighthouserc.json (the score budget), docs/DESIGN.md (contrast ratios for every token), and the seeded accessibility statement in scripts/seed/content.ts.
Reviewed September 3, 2026 · This chapter on its own page
Chapter 8
What does the migration itself look like?
Five phases over about four months, three payments, six named roles on your side. The schedule is set by content, approvals, and access, not by the code.
# What does the migration itself look like?
Five phases over about four months: two weeks to prepare, four to settle the content model and the design system, seven to build and migrate, one to launch, then sixty days of hypercare. Three payments, at signature, at staging sign-off, and at launch. On my side it's one engineer plus a designer; on yours it's one decision-maker, one content owner, a clinical reviewer, whoever signs off for compliance, and someone in IT. The code is the predictable part. The schedule is set by the three things clients underestimate every time: content, approvals, and access.
I wrote the month-by-month version of this in What a four-month healthcare site migration actually looks like. This chapter is the other half: who does what, which decisions are yours and when they're due, and what you're supplying.
What are the phases, and how long is each?
Fourteen weeks to launch, then sixty days of hypercare, with a named exit criterion for each phase so nobody has to argue about whether it's done.
- Prepare (weeks 1 to 2). Access granted per the checklist, accounts moved into your organization's name, content freeze rules agreed, staging environment up. Exit: kickoff complete. First payment, 40 percent, on signature.
- Model and design system (weeks 3 to 6). Content model finalized (chapter 3), Studio configured, design implemented as components. Exit: your team has used the Studio on staging and signed off the model.
- Build and migrate (weeks 7 to 13). Templates, content migration, integrations, redirect map (chapter 4), forms and consent (chapter 5), accessibility and performance passes. Exit: content QA complete and staging sign-off. Second payment, 40 percent.
- Launch (week 14). Redirect tests, DNS, analytics and consent verified, launch checklist signed. Exit: site live. Final payment, 20 percent. Chapter 9 covers the week itself.
- Hypercare (60 days). Fixes without change requests, two training sessions, written guide, search traffic watched against the pre-migration baseline. Exit: day 60, with a Platform Care proposal if you want ongoing support.
The assessment sits in front of all of this and produces the inventory (chapter 2) and the fixed price; its fee is credited against the build. Sites with fewer than fifty pages and no integrations run shorter. Sites with several hundred pages, a directory, and five integrations run to five months. Four is the middle.
Who does what?
Two people on my side, six roles on yours, and the six can be fewer people as long as each role has a name.
On my side: me, for everything technical from the content model to the redirects to the training, and a designer, either yours or one I've shipped with before, working from the content model so the design and the structure match.
On yours:
- Decision-maker. One person who can say yes. Committees review; one person decides. If nobody fits that description, the timeline is fiction.
- Content owner. Owns the keep, rewrite, or retire call on every page and the schedule for the rewrites. The biggest job on your side, and the one most often left unassigned.
- Clinical reviewer. A clinician who confirms condition and service content is still accurate and whose name goes in the reviewedBy field. Usually a part-time role; needs to know in month one that the pages are coming.
- Compliance or legal. Reviews the forms, the analytics plan, and the legal pages, and signs off in writing. Named at kickoff so their review time is in the plan, not a surprise in month four.
- IT. DNS, certificates, single sign-on if the Studio uses it, and any firewall or email rules the forms touch. Confirms in month three that DNS can be changed on the launch date and by whom.
- Vendor contacts. A name at each system being integrated (scheduling, CRM, careers, email), and someone on your side willing to chase them.
Which decisions are yours, and when are they due?
About ten, each with a week attached, and each with a turnaround written into the statement of work.
- Week 1: who fills each role above; what the launch window can't collide with.
- Week 2: which old pages are retired rather than migrated (the inventory's kill column).
- Weeks 3 to 4: content model sign-off, section by section.
- Weeks 4 to 6: design direction, then page designs, reviewed within a few days of delivery.
- Weeks 6 to 8: compliance sign-off on the forms and analytics plan, in writing.
- Weeks 7 to 12: the content edits themselves, on the schedule the content owner set in week 2.
- Week 13: staging sign-off from everyone who has to approve, feedback in one place.
- Week 13: launch date and go or no-go.
- Launch day: the DNS change, or written authorization for me to make it.
- Hypercare: anything that isn't working, reported the day your team notices.
The turnaround that goes in the SOW is a week for each. A decision that takes three weeks moves the date by two; nothing else on the plan can absorb it, because everything after it depends on it. I'd rather write that down at the start than discover it in month three.
What are you supplying?
Content, approvals, and access, plus a few things that are easy to forget.
- Content. The rewrites, the updated bios, the clinical review. On a 150-page site, expect roughly forty pages to need a real editorial pass. The migration script moves everything; it doesn't make it good.
- Approvals. Design reviews in days, not weeks. Compliance sign-off in month two, not month four. Staging sign-off from every approver at once, not one at a time.
- Access. Everything on the chapter 2 access checklist: registrar, DNS, host, CMS, analytics, Search Console, tag manager, each integration. Accounts registered to former employees transferred before the build depends on them.
- Brand assets. Guidelines, logos, fonts and their licences, photography and its usage rights.
- A launch-day person. Someone reachable for the few hours around the DNS change.
- An announcement plan, if there is one, timed a few days after launch rather than the morning of.
Why do content, approvals, and access get underestimated?
Because each one looks like a small task from the outside and is a queue of other people's time from the inside.
Content looks like updating a few pages. It's forty pages, four authors, one clinician, and a schedule nobody wrote down. The fix is a named owner with the authority to decide and the time to do it, and a page count agreed in week two.
Approvals look like "legal will take a look." It's a review of every form, every script, and every legal page, by people who have a hospital to run. The fix is naming them at kickoff and putting the forms and analytics plan in front of them in month two, when a no is cheap.
Access looks like sending over a login. It's a registrar account in the name of an agency that closed, a Search Console property owned by a marketing contractor from 2021, and a scheduling tool whose admin left. The fix is the checklist in week one and the rule that every account ends up in your organization's name before launch, whatever it takes to get there.
None of these is a technical problem, and no amount of engineering speed fixes them. That's why a plan that promises six weeks either skips them or pushes them past launch, where they become yours.
What does the weekly update look like?
A short written note, the same day every week, with four headings: done, next, waiting on, and date impact.
"Waiting on" is the important one. It names the decision or the deliverable and the person, and "date impact" says what it costs if it doesn't arrive this week. There's a call if something needs discussing; otherwise the note is the meeting. Decisions get logged with a date, so when someone asks in month four why a page was retired, the answer is in the log from week two.
What changes the timeline?
Five things, and four of them are on the client side.
Late content is the usual one. Vendor integrations are second: a scheduling vendor's API team on a six-week backlog is not something either of us controls, so integrations start in month two and get flagged in the update the week they stall. Design review loops are third. Scope additions are fourth; the price is fixed, so anything not in the inventory is a change order with a cost and a date impact, which is the point of the inventory. The fifth is a launch week that collides with something on your side, a board meeting or an audit or a conference, which is why the launch window is a week-one question.
What doesn't change the timeline: the build itself. With the starter, the content model and the components are days of work, not weeks. The four months is a plan for the human parts to happen properly.
Checklist
Before kickoff
During the build
Before launch - [ ] Staging reviewed by every approver, feedback in one place, signed off - [ ] IT has confirmed who changes DNS and when - [ ] Redirect map read and confirmed by the content owner - [ ] Launch-day contact and hypercare reporting path agreed Companion code in the Healthcare Sanity Starter: docs/MIGRATION.md (the launch checklist by phase). The month-by-month narrative is in the four-month migration post; the assessment that produces the plan is described at /assessment.
Reviewed August 29, 2026 · This chapter on its own page
Chapter 9
What happens in launch week and the month after?
Launch is a DNS change on a site finished weeks earlier. The month after is reading reports: 404s, impressions, form receipts. Old host off after 30 clean days.
# What happens in launch week and the month after?
Less than you'd expect on the day, and more than you'd expect in the month after. Launch is a DNS change on a site that has been complete on staging for weeks: the redirects are tested, the forms have been submitted, the analytics gate has been watched with the network panel open. What follows is thirty days of reading reports, because the things a migration gets wrong show up in Search Console and in form receipts, not on launch day. The old host stays alive, quietly, until those reports have been clean for a month.
This chapter is the launch checklist from the starter's MIGRATION.md with the reasons attached. If you only read one section, read the last one: the one about what you're watching for afterwards.
What has to be true before launch week starts?
The site is finished on staging and three checks have passed there, so launch week is configuration, not building.
- The URL parity check exits clean against the preview. The starter's check-url-parity script reads the old site's sitemap and requests every path on the new host; anything that isn't a 200 or a permanent redirect fails it. This is the launch gate from chapter 4. Run it until it passes, then don't touch the redirect map again without running it again.
- Every form has been submitted with test data and landed where it should, and nowhere else, per chapter 5.
- Staging sign-off is in writing from everyone who had to approve, per chapter 8, including the compliance sign-off and the content owner's confirmation of the redirect map.
If any of those is still open, the launch date moves. A launch with a known-failing parity check is a decision to lose rankings on purpose.
What gets configured in launch week?
Seven settings, each of which quietly breaks something if it's wrong, which is why they're a list and not a memory.
- The production site URL. Canonicals, the sitemap, and every piece of structured data derive from one setting. If it still points at the preview domain, every page tells search engines its real address is somewhere else.
- The form receiver. Pointed at the real destination, with a test submission confirmed at the receiving end, and the receiver covered by a business associate agreement if it could ever see clinical text.
- Real bot-protection keys on the forms. The starter ships with test keys that always pass; leaving them in production means the forms are open to anything.
- The analytics id, and then the check that matters: a fresh browser profile, the network panel open, and nothing third-party loads before consent is given.
- The CMS's allowed origins include the production domain, or the Studio and live preview stop working the moment DNS changes.
- The revalidation webhook points at the production domain with its secret set, so publishing in the CMS updates the site in seconds instead of on the next deploy. The symptom when this is missed is an editor who publishes, sees nothing change, and stops trusting the tool.
- robots.txt and the sitemap render on the preview domain and list the right URLs, with no leftover "disallow everything" rule from staging.
None of these takes more than a few minutes. All of them have taken sites down for a day when skipped.
What happens on launch day?
Point DNS, wait for the certificate, run the parity check against the live domain, submit the sitemap, spot-check the rich results, and send a form from a phone.
The DNS change is the launch. The person who makes it was named in week one; if it's me, that authorization was in writing. Certificates on modern hosts issue within minutes; until then some visitors see a warning, which is why the change happens early on a quiet day, not Friday afternoon.
Then the same parity check that gated the launch runs against the live domain, because the CDN and host configuration can differ from the preview's. Then the new sitemap goes into Search Console, and the old property stays open: its data is the baseline for the next month's comparison. Then a spot check with the rich results test on one provider page, one location page, one FAQ page, and one post, because the structured data that earns a clinic its map result and a physician their profile card is the easiest thing to lose silently.
Last, a form submitted from a phone, on cellular, not the office network. It exercises the consent banner, the bot check, the receiver, and the notification email in one go, on the device most patients actually use.
The old site stays reachable at a private address. Nobody links to it; it's there so a missed asset can be found and copied over in the first week rather than reconstructed.
What are you watching in the week after?
Four things, daily for the first week and then weekly: 404s in Search Console, impressions against the baseline, form receipts, and Core Web Vitals as real traffic arrives.
- Coverage report, 404s. Each one is a URL the inventory missed, and each one becomes a row in the redirect map the same day. The first week is when the long tail of old URLs shows up: links from newsletters, PDFs, partner sites, bookmarks. Expect a handful. Expect zero after the second week.
- Impressions, week over week. A dip of 10 to 20 percent for a couple of weeks is normal; the engine is re-crawling and re-evaluating every page at its new address. A cliff, meaning half or more, is a redirect problem or a canonical pointing at the wrong domain, and it's found by reading the top pages report against the old property.
- Form receipts. Not "did the form work on launch day" but "is a submission arriving at the receiver every day it used to". A form that silently posts into nothing is the failure that costs a clinic patients without anyone noticing.
- Core Web Vitals in field data, which only exists once real visitors arrive. The lab numbers from CI told you the site could be fast; the field numbers tell you whether it is for patients on phones.
The weekly update from chapter 8 continues through this period, with the same four headings. Most of what's in it is "nothing to report", and that's the point of writing it down.
When do you retire the old host?
After thirty days of clean reports, and the redirects live for much longer than that.
Thirty days covers a full crawl cycle for most sites and at least one round of every scheduled email and campaign that might link to an old URL. After that, the old host is switched off. What isn't switched off is the redirect map: it stays in the new site indefinitely, because the links that point at old URLs are on other people's sites and they don't get updated. A redirect map that's removed after a year is how the second migration loses the traffic the first one preserved.
What does done look like?
Sixty days after launch, per chapter 8: the site is stable, the team is publishing without asking for help, search traffic has settled at or above the pre-migration baseline, and every form has a receipt trail. That's the end of hypercare. Anything after that is upkeep, which is chapter 10.
Checklist
Before launch week
Launch week
Launch day
The month after - [ ] 404s reviewed daily for a week, weekly after; each one added to the redirect map - [ ] Impressions compared to the baseline weekly; a cliff investigated the same day - [ ] Form receipts confirmed daily for a week - [ ] Core Web Vitals field data reviewed once traffic arrives - [ ] Old host retired after 30 clean days; redirects kept indefinitely Companion code in the Healthcare Sanity Starter: docs/MIGRATION.md (this checklist, in the repo), scripts/check-url-parity.ts (the launch gate).
Reviewed August 29, 2026 · This chapter on its own page