vibeblame
Guides

Next.js security checklist: 10 things to check before you ship

A practical pre-deploy checklist for Next.js apps: source maps, leaked API keys, security headers, TLS, NEXT_PUBLIC_ secrets, server actions, CORS, and SEO. Each item with a quick way to verify.

Shipping a Next.js app is easy. Shipping it safely is the part that gets skipped — especially when most of the code was generated for you. This is a practical pre-deploy checklist: ten things that go wrong in real Next.js apps, why each one matters, and a quick command to verify you're clean.

Work through it top to bottom. Each item links to a full fix guide if you need the details.


1. Disable source maps in production

Source maps map your minified bundle back to original .tsx files. If they're public, anyone can read your source in DevTools — including comments, internal logic, and the exact place you hardcoded something you shouldn't have.

curl -I https://yoursite.com/_next/static/chunks/main.js.map
# Should return 404, not 200

Full fix: /guides/source-maps-production


2. Keep API keys out of the bundle

Anything that reaches the browser is readable by everyone. A Stripe secret key, an OpenAI key, or a database URL in your frontend bundle is already leaked — rotating it is the only fix.

# Search your built bundle for common key prefixes
grep -rE "sk_live_|AIza|ghp_|xoxb-" .next/static
# Should return nothing

Full fix: /guides/api-key-leaks


3. Add security headers (CSP, HSTS, …)

By default Next.js sends no Content-Security-Policy, no HSTS, no X-Frame-Options. That leaves you open to XSS, clickjacking, and protocol downgrade. Add them once in next.config.js.

// next.config.js
const securityHeaders = [
  { key: "X-Frame-Options", value: "DENY" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
]

module.exports = {
  async headers() {
    return [{ source: "/:path*", headers: securityHeaders }]
  },
}

Full fix: /guides/security-headers


4. Check your TLS/SSL setup

If you're on Vercel or Netlify this is handled for you. On a custom server or proxy, verify you're on TLS 1.2+ with a valid, non-expiring certificate and a matching domain.

echo | openssl s_client -connect yoursite.com:443 -servername yoursite.com 2>/dev/null | openssl x509 -noout -dates

Full fix: /guides/tls-ssl


5. Don't expose secrets via NEXT_PUBLIC_

The NEXT_PUBLIC_ prefix is not a security feature — it's the opposite. It tells Next.js to inline that value into the client bundle. Only put values there that you'd happily print on a billboard (a public publishable key, a public analytics ID).

VariableWhere it goesSafe for secrets?
DATABASE_URLServer only✅ stays private
NEXT_PUBLIC_API_URLInlined into the browser❌ public, never secret

Rule of thumb: if it's prefixed NEXT_PUBLIC_, assume the whole world can read it.


6. Protect server actions & route handlers

Server Actions and app/api/* route handlers run on the server, but they're still public HTTP endpoints. Anyone can call them directly — not just your UI. Authenticate and authorize inside the action, never rely on the button being hidden.

"use server"
export async function deleteAccount(id: string) {
  const session = await auth()
  if (!session) throw new Error("Unauthorized")
  if (session.userId !== id) throw new Error("Forbidden")
  // ... only now do the work
}

7. Validate env vars at build time

A missing or misspelled env var shouldn't blow up in production at the worst moment. Validate the whole set at startup so the build fails fast instead.

// env.ts
import { z } from "zod"

export const env = z
  .object({
    DATABASE_URL: z.string().url(),
    STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
  })
  .parse(process.env)

Import env instead of reading process.env directly, and a bad config never reaches users.


8. Lock down CORS

If you expose an API for other origins, don't reflect every Origin back with Access-Control-Allow-Origin: * while also allowing credentials — that combination leaks authenticated responses to any site. Allow an explicit list of origins.

const allowed = ["https://yourapp.com"]
const origin = req.headers.get("origin")
if (origin && allowed.includes(origin)) {
  res.headers.set("Access-Control-Allow-Origin", origin)
}

9. SEO & meta basics

Security done, don't ship invisible. Every page needs a unique <title>, a <meta name="description">, one <h1>, Open Graph tags, and a canonical URL. Next.js makes this declarative via the metadata export.

export const metadata = {
  title: "My Page",
  description: "A clear 120–160 character summary.",
  alternates: { canonical: "https://yourapp.com/page" },
  openGraph: { title: "My Page", images: ["/og.png"] },
}

Full fix: /guides/seo-meta-tags


10. Scan before you ship

Checklists are easy to half-finish. The fastest way to know you actually closed every hole is to scan the deployed URL and read the report.

Paste your production URL into vibeblame — it checks source maps, leaked keys, headers, TLS, and SEO in about 30 seconds and hands you an AI fix prompt for anything it finds.

Next.js security checklist: 10 things to check before you ship | vibeblame