Healthcare Practice: WordPress to Next.js + Payload CMS Migration
A mid-sized orthopedic practice in the Southeast came to us with a problem that's painfully common in healthcare: their WordPress site was slow, their patient intake process was a nightmare of PDF downloads and fax-backs, and their IT compliance officer was losing sleep over HIPAA exposure. Six months later, they had a Next.js frontend, Payload CMS backend, HIPAA-safe patient intake forms, and Lighthouse scores that made their competitors' sites look like they were running on dial-up. Here's exactly how we did it.
Table of Contents
- The Starting Point: What We Were Working With
- Why Next.js and Payload CMS
- HIPAA Considerations in a Headless Architecture
- The Patient Intake Redesign
- Performance Results and Lighthouse Scores
- Migration Strategy: Zero Downtime, Zero Lost Rankings
- Technical Architecture Deep Dive
- Lessons Learned
- FAQ

The Starting Point: What We Were Working With
The practice—let's call them Southeastern Ortho (NDA, you know how it goes)—had been running WordPress since 2017. Their setup was typical for a healthcare practice that had grown organically without much technical oversight:
- WordPress 6.2 with 34 plugins (11 of which hadn't been updated in over a year)
- Shared hosting on a plan that cost $29/month
- Contact Form 7 handling patient inquiries—no encryption, no BAA with the hosting provider
- PDF intake forms that patients had to download, print, fill out by hand, and either fax or bring to appointments
- Page load times averaging 6.8 seconds on mobile
- Lighthouse mobile score: 38
That Lighthouse score isn't a typo. Thirty-eight. The site had unoptimized hero images (one was a 4.2MB PNG), render-blocking CSS from five different plugins, and jQuery loading three times because of plugin conflicts.
But the real problem wasn't performance. It was risk.
Their contact forms were collecting patient names, phone numbers, and sometimes medical complaint descriptions. That data was flowing through an unencrypted form plugin, stored in a WordPress database on shared hosting, and backed up to a service with no Business Associate Agreement (BAA). Their compliance officer had flagged this, and their malpractice insurance carrier was asking pointed questions.
The Brief
The practice needed:
- A fast, modern website that reflected the quality of their care
- HIPAA-safe patient intake forms that replaced the paper process
- A CMS their office manager could update without calling a developer
- Better SEO performance (they were losing local search rankings to newer practices)
- All of this without breaking the bank—they're a medical practice, not a tech startup
Why Next.js and Payload CMS
We evaluated several architecture options. Here's the honest comparison we presented to the client:
| Option | Pros | Cons | Est. Cost |
|---|---|---|---|
| WordPress rebuild (new theme + plugins) | Familiar to staff, lower upfront cost | Same HIPAA risks, performance ceiling, plugin dependency | $15-25K |
| Webflow + third-party forms | Fast to build, good performance | Limited HIPAA compliance options, ongoing per-seat costs, vendor lock-in | $20-30K |
| Next.js + Payload CMS | Full control, HIPAA-safe architecture, best performance | Higher upfront investment, requires hosting management | $35-50K |
| Next.js + Sanity | Great editing experience, good ecosystem | Sanity's pricing scales with usage, PHI handling concerns with cloud-hosted CMS | $30-45K |
We recommended Next.js with Payload CMS, and here's why.
Next.js Was the Right Frontend
Next.js 14 (this project started in late 2024, and we've since been running on 15) gave us exactly what a healthcare site needs:
- Static generation for content pages — doctor bios, service descriptions, location info. These pages rarely change, so we pre-render them at build time. Zero server computation at request time means fast TTFB.
- Server Components for dynamic content — appointment availability, blog posts, and the intake form logic all benefit from server-side rendering without shipping unnecessary JavaScript to the client.
- Image optimization out of the box —
next/imagewith automatic WebP/AVIF conversion replaced those 4MB PNGs with properly sized, lazy-loaded images. - Middleware for security headers — we use Next.js middleware to set strict CSP headers, HSTS, and other security headers that HIPAA auditors love to see.
If you're curious about our approach, we've documented our Next.js development capabilities in more detail.
Payload CMS Was the Right Backend
We chose Payload CMS 3.0 over other headless options for several reasons specific to healthcare:
Self-hosted by design. Payload runs on your own infrastructure. This is non-negotiable for HIPAA. When you're handling Protected Health Information (PHI), you need to know exactly where your data lives, who has access, and be able to sign a BAA with your infrastructure provider. Cloud-hosted CMS platforms like Contentful or Sanity store data on their servers—and while some offer HIPAA compliance at enterprise tiers, the cost is typically 3-5x what you'd pay to self-host Payload on a HIPAA-eligible provider.
TypeScript-native. Payload's config is just TypeScript. This means our content models, API responses, and frontend types all share the same source of truth. When the office manager adds a new field for "insurance pre-authorization number" to the intake form, our frontend knows about it immediately through generated types.
Built-in access control. Payload's field-level access control meant we could create roles where the marketing person could edit blog posts and service pages, but couldn't touch patient intake data. The office manager could view intake submissions but couldn't modify the form structure. This granularity matters when you're documenting access controls for compliance.
Rich text done right. Payload uses Lexical (previously Slate) for rich text, and the editing experience is genuinely good. Our client's office manager, who'd been using WordPress for years, was comfortable in Payload's admin panel within a single 45-minute training session.
We work with Payload and other headless CMS platforms regularly—you can see more about our headless CMS development approach.
HIPAA Considerations in a Headless Architecture
Let me be clear about something: no technology stack is "HIPAA compliant" by itself. HIPAA compliance is an organizational practice, not a software feature. What a tech stack can be is "HIPAA-safe"—meaning it doesn't introduce unnecessary risk and supports the administrative, physical, and technical safeguards that HIPAA requires.
Here's what we implemented:
Infrastructure
- Hosting: AWS with a signed BAA. We used ECS Fargate for the Payload CMS container and deployed the Next.js frontend to Vercel (which doesn't handle PHI—important distinction).
- Database: Amazon RDS PostgreSQL with encryption at rest (AES-256) and encryption in transit (TLS 1.2+). Payload 3.0 supports Postgres natively, which was a big reason we waited for v3.
- File storage: S3 with server-side encryption, bucket policies restricting access, and versioning enabled for audit trails.
Data Flow for Patient Intake
This is where the architecture gets interesting. The patient intake form lives on the Next.js frontend, but we never send PHI through Vercel's infrastructure.
Patient Browser → HTTPS → API Route (Next.js on Vercel) → NO PHI stored here
↓
AWS API Gateway (with WAF)
↓
Lambda function (validates, encrypts)
↓
Payload CMS API (on ECS Fargate)
↓
RDS PostgreSQL (encrypted at rest)
The Next.js API route acts as a thin proxy. It validates the request structure (CSRF token, rate limiting, basic field validation) but doesn't log or store any PHI. The actual data processing happens entirely within AWS's HIPAA-eligible services.
Encryption Details
We implemented field-level encryption for the most sensitive data. Patient SSN fragments (last 4), insurance IDs, and medical complaint descriptions are encrypted at the application layer using AES-256-GCM before they even hit the database. This means even if someone got database access, they'd see encrypted blobs for sensitive fields.
// Simplified version of our field-level encryption hook in Payload
import { encrypt } from '../lib/crypto';
const PatientIntake: CollectionConfig = {
slug: 'patient-intake',
hooks: {
beforeChange: [
async ({ data }) => {
if (data.ssnLastFour) {
data.ssnLastFour = await encrypt(data.ssnLastFour);
}
if (data.medicalComplaint) {
data.medicalComplaint = await encrypt(data.medicalComplaint);
}
return data;
},
],
},
access: {
read: ({ req: { user } }) => {
return user?.role === 'office-admin' || user?.role === 'provider';
},
create: () => true, // Public form submission
update: ({ req: { user } }) => user?.role === 'office-admin',
delete: () => false, // Retention policy - no deletions through CMS
},
fields: [
// ... field definitions
],
};
Audit Logging
Every access to patient intake data is logged—who viewed it, when, and from what IP. We built a custom Payload plugin that writes to a separate audit log table. This table is append-only; even admin users can't modify or delete entries. During the practice's annual HIPAA risk assessment, this audit trail was specifically called out as a strength.

The Patient Intake Redesign
The old process: Patient downloads a 6-page PDF, prints it, fills it out with a pen (half the time illegibly), brings it to the office, and a staff member manually enters it into their EHR system. Average time from download to EHR entry: 3-5 business days.
The new process: Patient receives a text message or email link 48 hours before their appointment, completes the multi-step form on their phone in about 8 minutes, and the data is available in the practice's system before they walk through the door.
Form UX Decisions
We broke the intake form into 7 steps:
- Identity verification (name, DOB, contact info)
- Insurance information (carrier, ID, group number, photo upload of card)
- Medical history (conditions checklist, surgical history)
- Current medications (with autocomplete from an open formulary database)
- Reason for visit (free text + body diagram for pain location)
- Consent and agreements (e-signature capture)
- Review and submit
A few UX details that made a real difference:
- Progress indicator showing "Step 3 of 7" reduced abandonment by roughly 40% compared to our initial prototype that showed all fields at once. We A/B tested this during a soft launch.
- Insurance card photo upload with automatic cropping and a preview. Patients photograph the front and back of their card. This alone eliminated about 60% of front-desk data entry.
- Medication autocomplete using the RxNorm API. Instead of patients trying to spell "hydroxychloroquine," they type "hydro" and select from a filtered list. This reduced medication entry errors significantly.
- Session persistence — if a patient starts the form and gets interrupted, their progress is saved (encrypted in sessionStorage, never localStorage) for 30 minutes. They can resume where they left off.
// Medication autocomplete using RxNorm API
const useMedicationSearch = (query: string) => {
return useQuery({
queryKey: ['medications', query],
queryFn: async () => {
if (query.length < 3) return [];
const res = await fetch(
`/api/medications/search?q=${encodeURIComponent(query)}`
);
return res.json();
},
staleTime: 1000 * 60 * 5, // Cache for 5 minutes
enabled: query.length >= 3,
});
};
The server-side medication search endpoint queries RxNorm through our AWS backend, keeping the external API call away from the client and allowing us to cache results.
Performance Results and Lighthouse Scores
Here's the before and after:
| Metric | WordPress (Before) | Next.js + Payload (After) | Improvement |
|---|---|---|---|
| Lighthouse Mobile | 38 | 94 | +147% |
| Lighthouse Desktop | 61 | 99 | +62% |
| First Contentful Paint (Mobile) | 4.2s | 0.8s | -81% |
| Largest Contentful Paint (Mobile) | 8.1s | 1.4s | -83% |
| Total Blocking Time | 1,840ms | 45ms | -98% |
| Cumulative Layout Shift | 0.34 | 0.01 | -97% |
| Time to Interactive | 9.3s | 1.2s | -87% |
| Page Weight (Homepage) | 4.8MB | 340KB | -93% |
| Core Web Vitals Pass | No | Yes (all green) | — |
The mobile Lighthouse score of 94 (not 100, and I'll explain why in a moment) was measured on the patient intake page, which is the most JavaScript-heavy page on the site. Content pages like the homepage and service pages consistently score 98-100 on both mobile and desktop.
Why not a perfect 100 on mobile? Two reasons:
- The medication autocomplete widget requires client-side JavaScript that adds about 12KB gzipped to the intake form page.
- We use reCAPTCHA v3 on the intake form as a bot prevention layer, and Google's reCAPTCHA script is not exactly lightweight. We lazy-load it, but it still costs us a few points.
We considered removing reCAPTCHA to hit 100, but the security benefit outweighs the vanity metric. A healthcare intake form without bot protection is asking for spam submissions mixed in with real patient data.
Migration Strategy: Zero Downtime, Zero Lost Rankings
Migrating a healthcare practice website is stressful because downtime literally means missed patient appointments. Here's how we handled it:
Content Migration
We wrote a migration script that pulled content from the WordPress REST API and transformed it into Payload CMS documents. The script handled:
- 47 service pages
- 12 doctor/provider profiles
- 89 blog posts (with image re-hosting)
- 6 location pages
- All SEO metadata (titles, descriptions, canonical URLs)
URL Mapping
Every WordPress URL was mapped to its Next.js equivalent. We maintained the same URL structure where possible and set up 301 redirects in next.config.js for the handful of URLs that changed:
// next.config.js
const redirects = async () => [
{
source: '/services/:slug',
destination: '/orthopedic-services/:slug',
permanent: true,
},
{
source: '/team/:slug',
destination: '/providers/:slug',
permanent: true,
},
// ... 23 more redirects
];
DNS Cutover
We used a blue-green deployment strategy. The new site ran in parallel for two weeks while we tested. On cutover day, we updated DNS records during a Sunday evening maintenance window. Total visible downtime: about 3 minutes (DNS propagation was fast because we'd pre-lowered TTLs to 60 seconds a week before).
SEO Results
Three months post-launch:
- Organic traffic increased 34%
- Average position for "orthopedic doctor near me" improved from position 14 to position 5
- Click-through rate from Google increased 28% (better Core Web Vitals = better mobile SERP experience)
- Zero 404 errors in Search Console for previously indexed URLs
Technical Architecture Deep Dive
For those who want the full picture:
┌─────────────────────────────────────────────┐
│ Vercel │
│ ┌─────────────────────────────────────────┐ │
│ │ Next.js 15 App Router │ │
│ │ - Static pages (ISR, 60s revalidation) │ │
│ │ - Server Components │ │
│ │ - API routes (proxy only, no PHI) │ │
│ └─────────────────────────────────────────┘ │
└──────────────────┬──────────────────────────┘
│ HTTPS
┌──────────────────▼──────────────────────────┐
│ AWS (HIPAA BAA) │
│ ┌──────────────┐ ┌─────────────────────┐ │
│ │ API Gateway │ │ CloudFront (assets)│ │
│ │ + WAF │ └─────────────────────┘ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ ┌─────────────────────┐ │
│ │ ECS Fargate │──│ RDS PostgreSQL │ │
│ │ (Payload 3) │ │ (encrypted) │ │
│ └──────┬───────┘ └─────────────────────┘ │
│ │ │
│ ┌──────▼───────┐ ┌─────────────────────┐ │
│ │ S3 (uploads) │ │ CloudWatch (logs) │ │
│ │ (encrypted) │ │ (audit trail) │ │
│ └──────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────┘
Monthly infrastructure cost: approximately $180-220/month on AWS (ECS Fargate is surprisingly affordable at this scale) plus $20/month for Vercel Pro. Compare that to the $29/month shared hosting they were on before—yes, it's more expensive, but they're getting HIPAA-eligible infrastructure, automatic scaling, and genuine security.
Lessons Learned
1. Start HIPAA conversations early. We spent three weeks on architecture planning before writing a single line of code. This saved us from at least two potential redesigns.
2. Payload CMS v3 was worth the wait. We started this project when Payload 3.0 was in beta. There were rough edges—migration docs were incomplete, and some plugins hadn't been updated yet. But native Postgres support and the improved admin UI made it the right call.
3. Don't over-engineer the intake form. Our first prototype had conditional logic six levels deep. The office manager looked at it and said, "Can't we just ask the questions in order?" She was right. We simplified, and completion rates went up.
4. Healthcare clients need hand-holding on CMS training. We provided three training sessions instead of our usual one, plus recorded Loom videos for common tasks. The investment in training paid for itself in the first month when the office manager was able to add a new provider page without filing a support ticket.
5. Performance budgets are non-negotiable. We set a performance budget of <400KB page weight and <100ms Total Blocking Time at the start of the project. Every PR was checked against this budget in CI. The one time we tried to add an animated illustration library, it blew the budget, and we caught it before it shipped.
If you're considering a similar migration for a healthcare or regulated industry site, we'd be happy to talk through the specifics. You can reach out to us directly or check our pricing page for project ranges.
FAQ
Does using Next.js and Payload CMS automatically make a site HIPAA compliant? No. No technology stack is inherently HIPAA compliant. HIPAA compliance requires administrative safeguards (policies, training, risk assessments), physical safeguards (facility access controls), and technical safeguards (encryption, access controls, audit logs). What Next.js and Payload CMS give you is a flexible architecture where you can implement the technical safeguards properly—especially Payload's self-hosted nature, which lets you control where PHI lives.
Why not just use a HIPAA-compliant form service like Jotform or FormStack? You absolutely can, and for simpler use cases, that's a reasonable choice. We evaluated Jotform's HIPAA plan ($99/month) and FormStack ($83/month). The issue for this client was integration depth—they wanted the intake data to flow into a custom workflow that checked insurance eligibility in real-time and pre-populated their EHR system. Off-the-shelf form tools couldn't handle that without significant middleware, at which point you're building custom infrastructure anyway.
What was the total project cost and timeline? The project came in at approximately $42,000 over 14 weeks. This included discovery and architecture planning (3 weeks), design (2 weeks), development (7 weeks), and testing/migration (2 weeks). Ongoing hosting and maintenance runs about $250/month including infrastructure costs and a small support retainer.
Can Payload CMS handle multiple locations for a healthcare group? Yes. We built a "Locations" collection in Payload with fields for address, hours, providers, accepted insurance, and location-specific content. Each location gets its own page generated by Next.js with structured data markup (LocalBusiness schema) for local SEO. Adding a new location is as simple as creating a new entry in Payload's admin panel.
How do you handle patient data retention and deletion requirements? We implemented automated data lifecycle policies. Intake form submissions are retained for 7 years (matching the practice's state medical records retention requirement), after which they're automatically archived to S3 Glacier and eventually deleted. Patients can also request data access or deletion under state privacy laws—we built an admin workflow in Payload where staff can process these requests with a full audit trail.
What happens if the Payload CMS server goes down? The Next.js frontend serves statically generated pages from Vercel's CDN, so the main website stays up even if the Payload backend is completely offline. The patient intake form would be unavailable during a backend outage, but we've configured ECS with auto-restart policies, health checks every 30 seconds, and CloudWatch alarms that alert both our team and the practice's IT contact. In 6 months of production, we've had zero unplanned downtime.
Is this architecture overkill for a small practice with one location? It depends on what you're doing with patient data. If you just need a brochure site with a phone number and address, WordPress with a good theme is fine—you don't need any of this. But the moment you're collecting PHI through your website (intake forms, medical questionnaires, appointment requests with medical details), you need infrastructure that supports proper security controls. The architecture we built scales down well—a single-location practice would cost less on infrastructure because of lower traffic.
How did the migration affect Google rankings? We saw a brief (about 10-day) ranking fluctuation immediately after migration, which is normal. By week three, rankings had stabilized and were trending upward. By month three, organic traffic was up 34% and the practice's primary keywords had improved an average of 4 positions. The Core Web Vitals improvement was the biggest factor—Google had been penalizing their old site's poor mobile performance in search results.