Next.js Security Checklist for Production (2026)
A practical Next.js security checklist for 2026: patching, Server Actions, data access, secrets, proxy, headers, CSP, images, dependencies and monitoring.
Key Takeaways
- Next.js had two critical framework vulnerabilities in 2025: a middleware authorization bypass (CVE-2025-29927) and React2Shell (CVE-2025-55182, CVSS 10.0). Patching within days, not weeks, is the first control.
- Every Server Action is a public POST endpoint. Validate input and check authentication and authorization inside each one, not only in the UI.
- Put all data access in a server-only Data Access Layer that authorizes every read and returns only the fields the page needs.
- Never rely on middleware (now
proxy) alone for authorization. Use it for fast redirects; enforce access where the data is read.- Anything prefixed
NEXT_PUBLIC_is inlined into the browser bundle. Keep secrets unprefixed and server-side.- Add security headers and a nonce-based Content Security Policy, restrict
next/imageremote sources, and run self-hosted apps as a non-root user with limited outbound access.
A Next.js security checklist is a list of controls that keep a Next.js application safe in production: framework patching, server-side authorization, secret handling, HTTP headers and hosting hygiene. Next.js blurs the line between server and client code in one codebase, which is productive and easy to get wrong. This checklist is what we look at first when we audit a Next.js application, ordered by how often each item turns up as a serious finding.
The checklist at a glance
| # | Check | Why it matters |
|---|---|---|
| 1 | Framework and React patched to the latest release in your line | Two critical Next.js/React vulnerabilities in 2025 were exploited within hours or days |
| 2 | Every Server Action validates input and checks auth itself | Server Actions are reachable by direct POST, whatever the UI shows |
| 3 | Data access goes through a server-only layer that authorizes each read | Prevents leaking records through any component or action |
| 4 | Authorization is not enforced only in middleware/proxy | A proxy check can be skipped; CVE-2025-29927 did exactly that |
| 5 | No secrets in NEXT_PUBLIC_ variables or client bundles | Those values are inlined into JavaScript anyone can download |
| 6 | Security headers and a nonce-based CSP | Limits the damage of injected scripts and clickjacking |
| 7 | next/image limited to specific remote hosts | A wildcard turns your server into an open image proxy |
| 8 | No unsanitized HTML rendering | dangerouslySetInnerHTML on user content is direct XSS |
| 9 | Rate limiting and bot protection on forms, actions and auth | Stops credential stuffing, spam and cost attacks |
| 10 | Hardened hosting: non-root, read-only filesystem, limited egress | Contains a compromise when a framework bug gets through |
| 11 | Logs, alerts and a written incident plan | You find out from monitoring, not from your customers |
In the Next.js applications we audited in 2026, 7 out of 10 had at least one Server Action that skipped an authorization check, and half exposed more data to the client than the page displayed. Those two items are where to start.
1. Patch the framework quickly
Two critical framework vulnerabilities in 2025 show why patch speed is the first control:
| Vulnerability | What it did | First fixed releases |
|---|---|---|
| CVE-2025-29927, middleware bypass (March 2025) | A crafted x-middleware-subrequest header let requests skip middleware entirely, including any authorization done there | 12.3.5, 13.5.9, 14.2.25, 15.2.3 |
| CVE-2025-55182, React2Shell (December 2025) | Unauthenticated remote code execution through React Server Components | React 19.0.1, 19.1.2, 19.2.1; Next.js 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7 |
According to Vercel's postmortem, apps hosted on Vercel were not affected by the middleware bypass, while self-hosted apps running next start or output: 'standalone' were. React2Shell affected everyone running React Server Components; our field notes cover what exploitation looked like.
What to put in place:
- Automated dependency updates with Dependabot or Renovate, with security updates merged and deployed the same day.
- Watch the advisories: the Next.js security advisories and the React blog.
- A deployment pipeline you trust, so shipping a patch at short notice is routine rather than risky.
- Upgrade to the newest patch in your release line, not the first fixed one: later patches often fix follow-up issues.
2. Treat every Server Action as a public endpoint
The Next.js data security guide is explicit: an exported Server Action is reachable by a direct POST request, even if no component in your UI calls it. Hiding a button is not access control.
Every Server Action should:
- Validate its input with a schema (for example Zod), never trusting types from the client.
- Authenticate the caller from the session.
- Authorize the specific action on the specific resource.
- Return only what the caller needs.
"use server";
import { z } from "zod";
import { getSession } from "@/lib/auth";
import { canEditInvoice, updateInvoice } from "@/lib/dal/invoices";
const Input = z.object({ invoiceId: z.string().uuid(), note: z.string().max(500) });
export async function addInvoiceNote(raw: unknown) {
const input = Input.parse(raw);
const session = await getSession();
if (!session) throw new Error("Unauthenticated");
if (!(await canEditInvoice(session.user, input.invoiceId))) throw new Error("Forbidden");
await updateInvoice(input.invoiceId, { note: input.note });
}The authorization check belongs to the action and the data, not to the page that renders the form. Our article on structuring authorization explains how to keep these checks consistent across a large codebase.
3. Put data access behind a server-only layer
The same guide recommends a Data Access Layer for new projects: a set of server-only functions that are the only code allowed to read the database, that check authorization on every call, and that return small data transfer objects instead of whole records.
- Start each module with
import "server-only", so importing it from a client component fails the build. - Return DTOs that contain only the fields the UI needs. A user object passed to a client component is serialized to the browser in full, including fields you never render.
- React's experimental taint APIs can flag objects that must never reach the client, as an extra safety net, not a replacement for filtering.
4. Don't rely on middleware (proxy) for authorization
Next.js 16 renamed middleware to proxy. It is a good place for fast, optimistic checks such as redirecting signed-out users to the login page. It is not a good place for your only authorization check: CVE-2025-29927 showed that middleware can be skipped, and the Next.js authentication guide recommends verifying the session again close to the data. Enforce access in the Data Access Layer and in each Server Action and Route Handler.
5. Keep secrets out of the browser
From the Next.js environment variables documentation: variables prefixed NEXT_PUBLIC_ are inlined into the JavaScript sent to the browser at build time. Anything with that prefix is public.
- Only analytics IDs, public keys and similar values should use the prefix.
- Search your built bundles for secrets before release:
grep -r "sk_live\|BEGIN PRIVATE KEY" .next/static. - Keep
.env*files out of Git, and rotate any secret that has ever been committed or exposed. - After a security incident, rotate everything the application could read, as Vercel recommended after React2Shell.
6. Set security headers and a Content Security Policy
Configure headers in next.config with headers():
| Header | Recommended value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains |
X-Content-Type-Options | nosniff |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | Disable what you don't use, e.g. camera=(), microphone=(), geolocation=() |
Content-Security-Policy | At minimum default-src 'self', frame-ancestors 'self', object-src 'none', base-uri 'self' |
Also set poweredByHeader: false to stop advertising the framework.
A strict CSP needs a per-request nonce so that only your own scripts run. The Next.js CSP guide shows how to generate one in proxy and pass it to the app. Nonces force dynamic rendering; for mostly static sites, a policy that allows 'self' plus an explicit list of third-party origins is a reasonable first step.
7. Restrict next/image remote sources
The image optimizer fetches and resizes remote images on your server. List exact hosts in images.remotePatterns instead of a wildcard; otherwise anyone can use your server, and your bandwidth bill, as an image proxy.
8. Never render unsanitized HTML
React escapes text by default. dangerouslySetInnerHTML turns that off. If you render user content, CMS rich text or Markdown as HTML, sanitize it first (for example with rehype-sanitize or DOMPurify), and never pass request data into it directly.
9. Rate-limit forms, actions and authentication
Login, sign-up, password reset, contact forms and expensive Server Actions need rate limits and bot protection. Apply them at the edge (a CDN or WAF rule) or in the handler, and add a challenge such as Cloudflare Turnstile on public forms. Without them, credential stuffing and form spam are a question of when, not if.
10. Harden self-hosted deployments
If you run Next.js yourself in Docker or on a VPS:
- Run the Node.js process as a non-root user, with a read-only filesystem where possible.
- Never mount the Docker socket into the application container.
- Limit outbound network access to what the app needs. In the React2Shell incidents we handled, unrestricted egress is what let attackers download miners and second-stage tools.
- Keep the app behind a reverse proxy or CDN, and strip internal headers such as
x-middleware-subrequestat the edge.
11. Monitor, and know what you'll do when something happens
- Centralized logs for the app and the proxy, kept long enough to investigate.
- Alerts on unusual CPU usage, unexpected outbound connections and spikes in 4xx/5xx responses.
- An inventory of secrets and where each one is used, so rotation takes hours, not days.
- A short written incident plan: who decides, who patches, who talks to customers.
Frequently asked questions
Is Next.js secure? The framework is actively maintained and patches critical issues quickly, but most real-world problems come from application code: Server Actions without authorization checks, data over-exposed to the client and secrets in public variables. A secure Next.js app needs the controls above, whatever the framework's own track record.
Is middleware enough to protect my pages? No. Use it for redirects and quick checks, and enforce authorization where data is read and changed. CVE-2025-29927 allowed middleware to be skipped entirely on self-hosted apps.
Do I need a Content Security Policy? For any application that handles accounts, payments or personal data, yes. It is the main defense that still works when an injection bug slips through.
How often should we review the security of a Next.js app? At every major framework upgrade, before launch of significant features, and at least once a year. Critical systems benefit from continuous dependency monitoring.
We run this checklist as part of every technical audit of a Next.js application, and our security incident response team handles the cases where something already went wrong. If you want your Next.js app reviewed, or kept patched under a maintenance arrangement, get in touch. For the broader picture of what an audit covers, see what a technical audit includes.
Sources
- Next.js, How to think about data security in Next.js
- Next.js, Authentication guide, Content Security Policy guide and environment variables
- Vercel, Postmortem on Next.js middleware bypass and GHSA-f82v-jwr5-mffw (CVE-2025-29927)
- React team, Critical Security Vulnerability in React Server Components (CVE-2025-55182)
- OWASP, Next.js Security Cheat Sheet