How to Handle PHI in Next.js: Best Practices for HIPAA Compliance

Product Pricing
Ready to get started? Book a demo with our team
Talk to an expert

How to Handle PHI in Next.js: Best Practices for HIPAA Compliance

Kevin Henry

HIPAA

May 10, 2026

9 minutes read
Share this article
How to Handle PHI in Next.js: Best Practices for HIPAA Compliance

Protecting Protected Health Information (PHI) in a Next.js application requires intentional design choices across your stack. HIPAA compliance depends on how you minimize, transmit, store, access, and observe PHI—not on any one framework feature. This guide shows you how to apply secure coding practices in Next.js so you can confidently meet organizational and regulatory expectations.

Data Minimization Strategies

Minimization reduces both breach impact and compliance scope. Start by mapping every place PHI could enter, flow through, or leave your app, then cut anything nonessential.

Keep PHI off the client

  • Prefer Server Components to render sensitive views; only send derived, non-identifying data to Client Components.
  • Never place PHI in props serialized to the browser, in Redux stores, or in local/session storage.
  • Disable analytics, error replay tools, and third-party scripts on pages that may display PHI.

Avoid PHI in URLs, HTML, and caches

  • Use opaque IDs instead of names or dates of birth in route params, query strings, and filenames.
  • Mark PHI responses as non-cacheable and non-indexable. Avoid shared caches and CDNs for PHI pages.
// app/api/patient/route.ts (example)
export const dynamic = 'force-dynamic';
export const revalidate = 0;

export async function GET() {
  return new Response(JSON.stringify({ /* redacted fields */ }), {
    headers: {
      'Cache-Control': 'no-store, private',
      'X-Robots-Tag': 'noindex, nofollow'
    }
  });
}

Collect only what you need—and only when needed

  • Shorten forms and validate both client- and server-side to reject unexpected PHI fields.
  • De-identify or tokenize where feasible so you reference PHI indirectly during routine operations.
  • Apply strict retention policies and automatic deletion for stale PHI backups and exports.

Encryption Standards

Encrypt PHI in transit and at rest with proven algorithms and careful key management. Strong defaults reduce mistakes and make reviews simpler.

In transit: enforce TLS 1.2 or higher

  • Terminate HTTPS with modern ciphers; prefer TLS 1.3 where supported, with TLS 1.2 as a minimum.
  • Enable HSTS to prevent protocol downgrades and mixed content on PHI routes.

At rest: use AES-256 encryption

  • Choose storage that offers encryption at rest using AES-256 encryption and protect application-level secrets separately.
  • For field-level encryption, use authenticated modes (for example, AES‑256‑GCM) and store IVs and auth tags with the record.
// Simple Node crypto example (field-level encryption)
import crypto from 'crypto';

const ALG = 'aes-256-gcm';
const key = Buffer.from(process.env.DATA_KEY_BASE64!, 'base64'); // 32 bytes

export function encrypt(plain: string) {
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv(ALG, key, iv);
  const enc = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return { iv: iv.toString('base64'), tag: tag.toString('base64'), data: enc.toString('base64') };
}

export function decrypt({ iv, tag, data }: { iv: string; tag: string; data: string; }) {
  const decipher = crypto.createDecipheriv(ALG, key, Buffer.from(iv, 'base64'));
  decipher.setAuthTag(Buffer.from(tag, 'base64'));
  const dec = Buffer.concat([decipher.update(Buffer.from(data, 'base64')), decipher.final()]);
  return dec.toString('utf8');
}

Key management and rotation

  • Keep keys out of code and client bundles. Load them from a secure secret manager at runtime.
  • Rotate keys regularly; use envelope encryption so you can rewrap data keys without rewriting all records.

Session protection with cookies

  • Store session identifiers in HttpOnly Secure cookies with SameSite=strict and short TTLs; never store PHI in cookies.
// app/api/session/route.ts
import { cookies } from 'next/headers';

export async function POST() {
  const token = await issueSessionToken();
  cookies().set('session', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    path: '/',
    maxAge: 60 * 60 // 1 hour
  });
  return new Response(null, { status: 204 });
}

Access Control Implementation

Authorization must be explicit and enforced server-side. Start with least privilege and expand only when justified.

Role-based access control

  • Define clear roles (for example, clinician, billing, admin) and permissions tied to specific resources and actions.
  • Augment role-based access control with attributes (patient relationship, facility, time window) for finer policies.

Authenticate before you authorize

  • Require strong, phishing-resistant MFA for workforce accounts accessing PHI.
  • Use short-lived sessions and rotate tokens on privilege elevation or sensitive operations.

Enforce policies in Next.js

  • Gate protected routes in middleware and re-check authorization in Route Handlers and Server Actions.
  • Return 403 for disallowed actions and avoid leaking record existence in error messages.
// middleware.ts (simplified)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { decodeSession, hasRole } from './lib/auth';

export function middleware(req: NextRequest) {
  const session = decodeSession(req.cookies.get('session')?.value);
  const url = req.nextUrl;

  if (url.pathname.startsWith('/app')) {
    if (!session) return NextResponse.redirect(new URL('/login', url));
    if (url.pathname.startsWith('/app/admin') && !hasRole(session, 'admin')) {
      return new NextResponse('Forbidden', { status: 403 });
    }
  }
  return NextResponse.next();
}

Secure Data Storage

Design storage to confine PHI, restrict access paths, and support incident response. Keep auditability and reversibility in mind.

Ready to simplify HIPAA compliance?

Join thousands of organizations that trust Accountable to manage their compliance needs.

Databases and schemas

  • Use separate schemas or databases for PHI versus operational metadata. Apply row-level security to enforce per-user or per-role access.
  • Encrypt volumes and backups with AES-256 encryption; test restore procedures regularly.
  • Normalize sensitive fields and store only what you must; prefer tokens or references to large documents.

Files and object storage

  • Keep buckets private by default. Serve files through short-lived, scoped pre-signed URLs and log every download.
  • Encrypt objects and disable CDN caching for PHI. Set Content-Disposition to prevent accidental inline rendering when appropriate.

Caching and background jobs

  • Avoid shared caches. For any cache containing PHI, encrypt values and set aggressive TTLs; prefer in-memory per-instance caches.
  • Sanitize job payloads and queues so PHI is not embedded in task metadata.

Logging and Monitoring Procedures

Observability must exist without exposing PHI. Build auditability into every privileged operation and track changes over time.

Design audit logs from day one

  • Record who accessed which record, what action they performed, when, how, and from where.
  • Store identifiers (for example, patient IDs) but not PHI values. Hash or tokenize sensitive identifiers in audit logs.

Retention, integrity, and access

  • Make audit logs append-only and tamper-evident. Restrict access via break-glass workflows and MFA.
  • Define retention aligned with policy, and monitor for unusual spikes or off-hours access.

Sanitize and structure logs

// lib/log.ts
function redact(input: string) {
  return input.replace(/(ssn|mrn|dob|name|email)\s*=\s*[^&\s]+/gi, '$1=[REDACTED]');
}

export function audit(event: string, data: Record<string, unknown>) {
  const entry = { ts: new Date().toISOString(), event, ...data };
  console.info(JSON.stringify(entry)); // ship to centralized store with access controls
}

Continuously review audit logs and wire alerts for high-risk events such as bulk exports, repeated denials, or disabled safeguards.

Content Security Policy Enforcement

A strong CSP limits where scripts, connections, and assets may load from, reducing the risk of PHI exfiltration via XSS or malicious third parties.

Build a restrictive default, then allow as needed

  • Block inline scripts by default; use nonces or hashes for any necessary inline code.
  • Limit connect-src to your own APIs; restrict img-src, media-src, and frame-ancestors to trusted origins.
  • Set base-uri 'none' and object-src 'none' to remove legacy attack surfaces.

Set headers in Next.js

// next.config.ts (excerpt)
export default {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: [
              "default-src 'self'",
              "script-src 'self' 'nonce-__NONCE__'",
              "style-src 'self'",
              "img-src 'self' data:",
              "connect-src 'self'",
              "frame-ancestors 'none'",
              "base-uri 'none'",
              "object-src 'none'",
              "form-action 'self'"
            ].join('; ')
          }
        ]
      }
    ];
  }
};

Pair CSP with subresource integrity for third-party assets you must use, and regularly test pages to ensure policies don’t regress.

Error Handling Techniques

Errors are a common source of leaks. Your handlers and UI should fail closed, reveal minimal information, and keep PHI out of every message and stack trace.

Server-side hygiene

  • Wrap Route Handlers and Server Actions with try/catch that converts exceptions to generic responses without PHI.
  • Return stable error codes for the client and stash full details in secure audit logs, not in responses.
// app/api/records/route.ts (pattern)
export async function GET(req: Request) {
  try {
    // ... fetch record ...
    return Response.json({ /* minimal, necessary fields only */ });
  } catch (err) {
    audit('api_error', { route: '/api/records', code: 'E_READ', detail: 'See server logs' });
    return new Response('Request failed', { status: 500 });
  }
}

Client messaging and boundaries

  • Show friendly, generic messages—never patient names, MRNs, or hints about record existence.
  • Use error.tsx and not-found.tsx to centralize fallback UI and avoid ad hoc error rendering.

Conclusion

HIPAA-ready Next.js apps keep PHI server-side, encrypt it with TLS 1.2+ in transit and AES-256 at rest, enforce role-based access control, store data in locked-down systems, maintain rigorous audit logs, apply a tight CSP, and sanitize every error path. Combine these controls with secure coding practices and a signed Business Associate Agreement where required to create a defensible, privacy-first application.

FAQs

What are the key HIPAA compliance requirements for Next.js applications?

Focus on least-privilege access, data minimization, encryption in transit (TLS 1.2 or higher) and at rest (AES-256 encryption), strong authentication with HttpOnly Secure cookies, comprehensive audit logs, and strict configuration of Content Security Policy. Pair technical controls with administrative safeguards—training, risk assessments, incident response plans, and a Business Associate Agreement with any vendor that touches PHI.

How can PHI be securely stored in Next.js?

Store PHI on encrypted backends using AES-256 encryption and limit access via role-based access control enforced in server-side code. Keep PHI out of caches and client storage, use private buckets with short-lived pre-signed URLs for files, encrypt backups, and ensure keys are managed and rotated securely. Avoid placing PHI in cookies or URLs; reserve cookies for authenticated sessions using HttpOnly Secure cookies.

What role do Business Associate Agreements play in PHI handling?

A Business Associate Agreement (BAA) contractually binds service providers—such as hosting, logging, email, or support platforms—to safeguard PHI under HIPAA. Without a BAA, you should not transmit or store PHI with that vendor. Ensure every third party involved in storage, processing, or transmission of PHI signs a BAA and supports required controls like encryption, audit logs, and access restrictions.

How to prevent PHI exposure in client-side code?

Render PHI with Server Components whenever possible and send only the minimum derived data to the browser. Never write PHI to localStorage, sessionStorage, or query strings; avoid including it in props and logs; and restrict third-party scripts with a strong CSP. Validate inputs and outputs rigorously and follow secure coding practices to prevent XSS, CSRF, and injection flaws that could expose PHI.

Share this article

Ready to simplify HIPAA compliance?

Join thousands of organizations that trust Accountable to manage their compliance needs.

Related Articles