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?

If most of your app was generated, read is an AI-generated app secure? alongside this checklist.

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

Middleware redirecting is fine for UX. It just can't be the only place you check who the user is.

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.

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.

  1. Add core headers via next.config.js or middleware: Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and a Referrer-Policy.
  2. Add a Content-Security-Policy. For the App Router, generate a per-request nonce in middleware and read it back with headers() — Next.js wires the nonce into its own scripts so you can drop unsafe-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

Keep secrets server-side. Check auth where the data lives, not in middleware. Authorize inside every Server Action. Sanitize HTML, ship a CSP, trim your props, patch your deps, and validate every input on the server.