Praneet Sah
Guide

Building a HIPAA-compliant Next.js app on Vercel

Vercel only signs a BAA on Enterprise — and that is just the first of several things a HIPAA-ready Next.js architecture needs. Here is the full picture: hosting tiers, encryption, audit logging, RBAC, and subprocessor BAAs.

Every few months someone shows me a healthcare product built on Next.js, deployed on Vercel's Pro plan, with patient data in the database, and asks me to "review the HIPAA setup." There usually isn't one. The app looks fine. The code is fine. But the foundational legal instrument — a Business Associate Agreement with the company running the servers — does not exist, and on the plan they are paying for, it cannot exist.

This guide covers what actually has to be true for a Next.js application handling protected health information to be defensible: the hosting tier problem, the parts of compliance that have nothing to do with hosting, a concrete architecture for Next.js and Postgres that keeps PHI access auditable, and the questions to ask your database vendor.

The gotcha nobody mentions until late: Vercel's BAA is Enterprise-only

Start here, because it changes budgets and sometimes changes platforms.

Under HIPAA, when a covered entity (a provider, a health plan, a clearinghouse) hands protected health information to a vendor that will store, transmit, or process it, that vendor becomes a business associate — and the relationship has to be papered with a Business Associate Agreement. The BAA is the contract in which the vendor accepts direct obligations: safeguard the data, restrict its use, report breaches, flow the same terms down to their own subcontractors.

No BAA, no lawful disclosure. It is not a formality you can retrofit after launch, and it is not something an infrastructure provider will sign because you asked nicely on a support ticket.

Vercel makes its BAA available on Enterprise plans. Hobby and Pro do not include one. That means an application processing PHI on a Pro deployment is, from a compliance standpoint, disclosing patient data to a vendor that has accepted no obligations regarding it. Auditors treat that as a finding. Health-system procurement teams treat it as a disqualification.

This surprises people because everything else about Pro feels production-grade. It has the same runtime, the same edge network, the same preview deployments. The gap is contractual, not technical, and technical excellence does not close it.

You have three honest options:

Move to Vercel Enterprise. You get the BAA and the configuration controls that come with it. Expect an annual contract and a sales conversation rather than a credit card. For a funded healthcare product this is usually the right answer — the developer experience Next.js teams rely on stays intact.

Split the architecture. Keep the marketing site, the docs, and any surface that never touches PHI on Vercel Pro. Move the authenticated application and its API to infrastructure where you already hold a BAA — AWS, GCP, or Azure with a HIPAA-eligible service configuration. This is more work and two deployment pipelines, but it is a legitimate pattern and it keeps costs proportionate to actual risk.

Host the whole Next.js app on BAA-covered infrastructure. Next.js runs perfectly well in a container. next start behind a load balancer on ECS, Cloud Run, or a Kubernetes cluster gives you the full framework with none of the platform-tier question. You give up some Vercel-specific ergonomics; you gain a single compliance boundary.

Whichever you pick, confirm the current terms with the vendor directly. Plan-tier compliance offerings are business decisions, and they change.

Hosting is one line item on a longer list

Getting the BAA signed feels like crossing the finish line. It is closer to qualifying for the race. HIPAA's Security Rule asks for administrative, physical, and technical safeguards; your host covers some of the physical and a slice of the technical. The rest is yours.

Encryption in transit and at rest

In transit is mostly solved: TLS everywhere, HSTS on, no mixed content, and — the part teams forget — encryption on the internal hops too. The connection from your Next.js server to Postgres needs TLS with certificate verification, not just sslmode=require pointed at whatever certificate answers. Same for the connection to your cache, your queue, and your object store.

At rest means the database volume, the backups, the object storage holding uploaded documents and images, and any log or export that captured PHI along the way. Managed database encryption covers the primary volume; it does not automatically cover the CSV someone exported to S3 for a data-migration script, or the nightly dump sitting in a bucket with default settings.

For especially sensitive columns, consider application-level encryption on top of disk encryption, so the plaintext is not readable by anyone with a database console. It complicates search and indexing, so apply it deliberately — Social Security numbers and free-text clinical notes, not every column in the table.

Audit logging of PHI access

This is the requirement teams most often discover during their first customer security review, and it is the hardest to add late.

You need to be able to answer, for any record, who accessed it, when, from where, and through what action. Not "the API received a request" — that is application logging. Audit logging means a durable, append-only record of PHI access that survives a redeploy, is not writable by application code that could be compromised, and can be produced during an investigation.

The retrofit is painful because access is scattered across dozens of query sites. Building it in from day one means routing PHI reads through a small number of chokepoints and instrumenting those chokepoints, which is a design decision you make once and then hold the line on.

Role-based access control and minimum necessary

HIPAA's minimum necessary standard says people should see the least PHI required to do their job. In practice that means a permission model with real granularity: a billing coder does not need clinical notes, a scheduler does not need lab results, and a support engineer does not need production patient data at all — they need a way to reproduce bugs without it.

Enforce authorization at the data-access layer, not in the UI. Hiding a tab is not access control. If a well-formed API request from a low-privilege session can return a record the user should not see, the tab does not matter.

BAAs with every subprocessor that touches PHI

Enumerate the outbound flows. A typical Next.js healthcare app talks to more third parties than the team remembers:

  • The database host
  • Object storage for uploads
  • Transactional email (appointment reminders are PHI)
  • SMS provider (same)
  • Error tracking — which will happily capture request bodies and URL parameters
  • Log aggregation
  • APM and session replay, which can record entire screens of patient data
  • Any LLM or transcription API you send clinical text to
  • Analytics, if any URL, event property, or user trait carries a patient identifier

For each: does it touch PHI, and is there a BAA? Vendors that will not sign one need to be removed from the PHI path — either replaced or configured so PHI never reaches them. Aggressive scrubbing before data leaves your process is a valid mitigation for error tracking and logging, but it has to be verified, not assumed. Test it with a synthetic record and read what actually arrived.

An architecture pattern for Next.js and Postgres

Here is the shape I would build toward. The organizing principle is simple: PHI has exactly one door, and the door has a camera on it.

Push PHI access to the server. With the App Router, keep PHI in Server Components and Route Handlers. Client Components receive rendered, minimized views — never a full patient object serialized into the RSC payload because it was convenient. Every field that crosses into the client is a field that lands in the browser cache and possibly in a session-replay recording.

One data-access layer, no exceptions. Create a module — call it the PHI repository — that owns every query touching patient data. Nothing else imports the database client. Enforce it with a lint rule so the rule survives contributor turnover.

Every repository call takes an actor and a purpose. Not getPatient(id) but getPatient(id, { actor, purpose }). The actor carries identity and role; the purpose is a coarse reason code (treatment, billing, scheduling, patient_self_access). This makes two things possible: authorization checked in one place against actor and purpose, and an audit record that is genuinely informative rather than a stream of anonymous reads.

Authorize inside the repository, before the query. The check is a function of actor role, purpose, and the relationship between actor and record. Denials get logged too — a burst of denials is one of the more useful breach signals you will have.

Write the audit entry as part of the same transaction as the read path's authorization decision, and ship it somewhere append-only. Storing audit rows in the same Postgres instance is a reasonable start; forwarding them to write-once storage with a retention policy is where you want to end up, so that database compromise does not also erase the record of the compromise. Each entry: timestamp, actor ID, actor role, patient/record ID, action, purpose, source IP, request ID, and outcome. No PHI values in the audit log itself — you are recording that a record was accessed, not duplicating its contents into a second, less-protected store.

Set request context once, at the edge of the request. A small context object established in middleware or at the top of the route handler carries the actor and request ID down to the repository, so no call site has to remember to thread it.

Keep PHI out of URLs. /patients/9f3c/labs puts a record identifier in browser history, in referrer headers, in access logs, and in every analytics tool that captures pathnames. Use opaque, non-sequential identifiers at minimum, and prefer POST bodies for anything sensitive enough that its mere identifier is a hint.

Watch your caching. revalidate, unstable_cache, and CDN caching are all excellent tools that will happily serve one patient's data to another if the cache key omits the actor. Default PHI routes to no-store and opt into caching deliberately, per route, with the user identity in the key.

Have a break-glass path. There will be an incident where someone legitimately needs elevated access. Design it now: time-boxed, explicitly requested, loudly logged, and reviewed after the fact. Teams without one end up sharing a production credential at 2am, which is exactly the event audit logging exists to prevent.

The database layer

Your database is the second BAA conversation, and it is the one that most often forces an architectural decision.

Supabase is the common choice for Next.js teams and does offer a HIPAA posture — historically as a paid add-on on higher-tier plans, with specific configuration requirements and a defined scope of covered services. If you are going this route, get the current terms in writing: which plan, which add-on, which services are in scope, and whether the features you depend on (edge functions, storage, realtime, auth) are all covered. Scope boundaries matter more than the headline "we support HIPAA."

Neon, PlanetScale, and other managed Postgres/MySQL providers vary widely. Some sign BAAs on enterprise tiers, some do not offer one at all. Ask before you build.

AWS RDS, Google Cloud SQL, and Azure Database sit under the cloud providers' overall BAAs, but only HIPAA-eligible services are covered. Each provider publishes a list, and using a non-eligible service for PHI puts you outside the agreement even though you hold one. Check the list, not the brand.

Self-managed Postgres on a VM puts everything on you: encryption configuration, patching, backup encryption, access logging, key management. Fine if you have the operational maturity, expensive if you do not.

Whatever you choose, the same questions apply. Will they sign a BAA, on which plan, covering which services? Are backups encrypted and where do they live? Is there database-level audit logging, and can you get at it? Who at the vendor can read your data, and is that access logged? What happens to your data on termination, and how quickly?

Preview deployments and non-production environments

Preview deployments are one of the best things about the Next.js workflow and one of the easiest ways to leak PHI. A pull request spins up a URL that is publicly reachable by default, and if that build points at a database seeded from a production snapshot, you have just published patient data to the internet on a guessable hostname.

Two rules keep this safe. First, non-production environments never contain real PHI — generate synthetic data that has the same shape and cardinality as production so the app behaves realistically, and make restoring a production snapshot into staging an action that requires deliberate approval rather than a convenient script anyone can run. Second, preview deployments are access-protected regardless, because "there is no real data in there" is a claim that stays true only until the first time someone is debugging a customer issue under pressure.

The same reasoning applies to local development. If reproducing a bug requires a developer to pull a production record onto a laptop, the fix is better synthetic fixtures and better structured logging, not a shared read-only credential.

Where teams actually get caught

The failures I see are rarely exotic. Analytics or session replay installed globally, capturing authenticated pages. Error tracking with request-body capture on, sending PHI to a vendor with no BAA. A staging environment loaded with a copy of production data, sitting on a Pro-tier deployment with no BAA and a shared password. Backups that are encrypted in the primary region and unencrypted in the copy someone made for a migration. Audit logs that exist but were never tested against a real "show me who accessed this record" question.

None of those are Next.js problems or Vercel problems. They are the ordinary consequence of treating compliance as a hosting checkbox rather than a property of the system.

Get the BAA — it is necessary and it is not sufficient. Then build the doors, put cameras on them, and check the footage before someone else asks to.

This guide is engineering guidance, not legal advice. Vendor compliance terms change; verify every vendor claim here against current documentation, and have your compliance posture reviewed by qualified counsel before handling real PHI.

Frequently asked

Can I use Vercel's Hobby or Pro tier at all if PHI is involved?
Not for anything that stores, transmits, or processes PHI. Vercel offers its Business Associate Agreement on Enterprise plans only, and without a signed BAA the vendor is not legally a business associate — which means routing PHI through it is a disclosure you are not authorized to make. You can absolutely use Hobby or Pro for a marketing site, a docs site, or a staging environment seeded with synthetic data, as long as no real patient data ever crosses that deployment. Verify the current tier requirement directly with Vercel before you build around it; vendor compliance offerings change.
Does using Vercel Enterprise alone make my app HIPAA compliant?
No. A BAA with your host covers your host. It says nothing about whether you encrypt PHI at rest, whether you log who read which record, whether a support engineer can query the production database unaudited, or whether the analytics script on your patient portal is quietly shipping URLs containing record IDs to a third party you never signed a BAA with. Hosting is one subprocessor in a chain. Compliance is a property of the whole system plus the policies and training around it.
What's the difference between HIPAA-compliant hosting and a HIPAA-compliant application?
HIPAA-compliant hosting means the infrastructure provider will sign a BAA and has controls — physical security, encryption, access management — that let you build compliantly on top. A HIPAA-compliant application is what you build: authorization that enforces minimum necessary access, an audit trail of PHI access that you can produce on demand, encryption of PHI in transit and at rest, breach detection, and data retention and disposal that match your policies. Compliant hosting is a prerequisite. It is not the deliverable.
Do I need a BAA with every third-party API my app calls?
With every one that creates, receives, maintains, or transmits PHI on your behalf — yes. That commonly includes your database host, your email and SMS providers, your error tracking service, your logging pipeline, your file storage, and any AI or transcription API you send clinical text to. It does not include vendors that never touch PHI. The practical exercise is to enumerate every outbound data flow in the app and mark each one PHI or not-PHI, then close the gaps.

Have a project like this?

Book a call

Praneet Sah

Independent app developer. Builds full-stack products end to end — web, iOS, Android, AI agents, telecom — and has shipped every project referenced on this page personally.