Next.js Security Checklist (2026): The Footguns AI Tools Ship
A concrete Next.js App Router security checklist for 2026 — the mistakes AI tools and copy-paste ship (leaked env vars, middleware auth, Server Actions), each with a one-line fix.
· 8 min read
AI coding tools are great at shipping a working Next.js app fast. They are not great at shipping a secure one. The same patterns show up again and again: a secret prefixed with NEXT_PUBLIC_, an auth check that lives only in middleware, a Server Action that trusts whoever calls it. None of these throw an error, so they sail straight to production.
This is a practical Next.js security checklist for the App Router (Next 15/16 era, 2026). Every item is a real mistake AI tools and copy-paste commonly ship, with a one-line fix. If you built with a copilot, walk this list — most apps fail at least two of them.
Not sure if you're building safely?
1. Don't let NEXT_PUBLIC_ leak your secrets
In Next.js, any env var prefixed NEXT_PUBLIC_ is inlined into the JavaScript bundle every visitor downloads. That prefix is meant for genuinely public config (a public API URL, an analytics ID) — but AI tools slap it on secret keys too, so your sk_live_… or service_role key ships to the world at build time.
Fix: keep secrets server-side with no NEXT_PUBLIC_ prefix, read them only in Server Components, Route Handlers, or Server Actions, and rotate anything that already leaked. Full walkthrough: the NEXT_PUBLIC_ leak trap.
2. Middleware is NOT an auth boundary
The most common AI-shipped pattern is one middleware.ts that redirects logged-out users, and no auth check anywhere else. That is not a security boundary. Middleware runs for routing and response shaping — it can be skipped, and in 2025 CVE-2025-29927 let attackers bypass it entirely with a crafted x-middleware-subrequest header, reaching "protected" routes with no credentials.
Fix: treat middleware as an optimization, not a gate. Verify the session where the data actually lives — inside the Server Component, Route Handler, or Server Action itself — ideally through a single Data Access Layer that every read and write passes through. And stay patched (see item 7); the fix shipped in 14.2.25 and 15.2.3.
Defense in depth
3. Server Actions are public endpoints — authorize inside them
A Server Action looks like a local function you import and call, so it feels private. It isn't. Every action compiles to a POST endpoint anyone on the internet can hit directly — no button, no form, no logged-in session required. AI tools almost never add auth inside them.
- Authenticate: re-check the session at the top of every action. Never trust that the client-side gate ran.
- Authorize: confirm this specific user may perform this specific action on this specific resource — not just "is logged in," but "owns this record" or "is an admin."
Fix: start each Server Action and Route Handler with a session lookup and an ownership/role check before it touches the database.
4. dangerouslySetInnerHTML and XSS
React escapes values by default, so {userInput} in JSX is safe. The moment you reach for dangerouslySetInnerHTML — often to render Markdown, rich text, or a CMS field — you turn that protection off. If any of that HTML comes from users, a <script> or an onerror handler runs in your visitors' browsers.
Fix: avoid dangerouslySetInnerHTML for anything user-influenced. If you must render HTML, sanitize it server-side first (for example with a maintained library like DOMPurify) and never interpolate raw input into href, src, or inline event handlers.
5. Ship security headers and a Content-Security-Policy
A default Next.js app sends almost no protective headers, and AI scaffolds rarely add them. A Content-Security-Policy is your best backstop against XSS: even if a bad script sneaks in, a strict CSP can stop it from executing or phoning home.
- Add core headers via
next.config.jsor middleware:Strict-Transport-Security,X-Content-Type-Options: nosniff,X-Frame-Options: DENY, and aReferrer-Policy. - Add a
Content-Security-Policy. For the App Router, generate a per-requestnoncein middleware and read it back withheaders()— Next.js wires the nonce into its own scripts so you can dropunsafe-inline.
Fix: follow the official Content-Security-Policy guide and start CSP in report-only mode so you can tighten it without breaking your app.
Check your Next.js app for these footguns — free, no signup
Trust scans your live site and your repo for leaked env vars, missing headers, and exposed secrets.
Run a free scan →6. Don't leak server-only data through props or serialization
When a Server Component passes data to a Client Component, everything in those props is serialized and shipped to the browser. Hand a full user or order row straight down — with a passwordHash, internal flags, or another user's email — and it's readable in the page payload, even if nothing renders it on screen.
Fix: select only the fields the client needs before you cross the boundary. Shape a small DTO (id, display name, the two fields you render) instead of spreading the whole database record into props. Treat React's server-only package as a guardrail to keep server modules from being imported into client code.
7. Keep dependencies patched (SCA)
CVE-2025-29927 is the reminder here: the framework itself gets vulnerabilities, and so do the dozens of transitive packages an AI-generated package.json pulls in. An app that was secure at scaffold time drifts out of date within weeks.
Fix: run npm audit (or your scanner of choice) regularly, pin and update Next.js promptly when security releases land, and turn on automated dependency updates so patches land without you remembering to look.
8. Validate input on the server (zod)
Client-side validation is a UX nicety, not a control — a direct POST skips it entirely. Since Server Actions and Route Handlers are reachable by anyone (item 3), any input they receive is untrusted: wrong types, missing fields, oversized payloads, or values crafted to slip past your logic.
Fix: parse and validate every input on the server with a schema library like zod before you use it. Reject anything that doesn't match, and derive your types from the schema so validation and TypeScript never drift apart.
Run the checklist automatically
You can walk these eight items by hand — but the ones that bite hardest (a leaked NEXT_PUBLIC_ key, a missing header, a stale dependency) are exactly what a scanner catches in seconds. Trust checks both your live site and your repo, then tells you which file to fix. Wiring security into an AI agent's workflow? Trust also ships as an MCP server so your assistant can scan as it builds.
The short version