Troja
All posts
VercelMar 22, 2026·14 min read

Vercel Deployment Security: The Production Checklist for Next.js

Vercel makes deploying trivial — and makes a few security footguns trivial too. Here's the production checklist: env scoping, headers, preview protection, and the bundle leak everyone hits.

By The Troja Team

Easy to deploy, easy to leak

Vercel's developer experience is excellent, which means the security mistakes are also easy to make at speed. This checklist covers the Vercel-and-Next.js-specific ones.

1. The NEXT_PUBLIC_ bundle leak

Any env var prefixed NEXT_PUBLIC_ is inlined into the client bundle and shipped to every visitor. Developers routinely prefix a secret to "fix" an undefined variable and silently publish it.

# Only PUBLIC values should appear here:
grep -r "NEXT_PUBLIC_" .env*

Rule: NEXT_PUBLIC_* is for non-secret config only (analytics IDs, public anon keys). A Stripe secret key, a service-role key, or a database URL must never carry that prefix. If one did, rotate it — it's already public.

2. Environment scoping

Vercel has three environments: Production, Preview, Development. Don't reuse production secrets in preview — preview URLs are easier to discover and share. Scope secrets per environment in the dashboard, and use separate API keys/databases for preview where you can.

3. Protect preview deployments

Every push spins up a public preview URL. If your previews touch real data or expose unfinished admin tooling, enable Deployment Protection (Vercel Authentication or password) so previews aren't crawlable by anyone with the URL.

4. Security headers via config

Set headers once in next.config.js so every route is covered:

// next.config.js
const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "X-Frame-Options", value: "DENY" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "Content-Security-Policy", value: "default-src 'self'" },
];

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

Confirm they actually ship:

curl -sI https://yourapp.vercel.app | grep -i "strict-transport\|content-security"

5. Lock down Route Handlers and Server Actions

  • Every app/api/*/route.ts is public by default — add auth checks inside.
  • Server Actions in modern Next.js validate the Origin against allowedOrigins, but you still must authorize the action (is this user allowed to do this?).
  • Don't return internal error details to the client.

6. Don't trust middleware as your only auth gate

Next.js middleware runs on the edge and is fine for redirects, but it has historically had bypass edge cases and doesn't see everything. Enforce authorization at the data layer too (in the route handler / Server Component that reads data), not only in middleware.

7. Cron and webhook endpoints need secrets

Vercel Cron and external webhooks hit public URLs. Require a shared secret:

export async function GET(req: Request) {
  if (req.headers.get("authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response("Unauthorized", { status: 401 });
  }
  // ... do the job
}

For Stripe and similar, verify the signature, not just a bearer token.

8. Source maps and verbose builds

Avoid shipping source maps that expose your server logic to the browser in production, and make sure your error pages don't print stack traces.

9. Custom domains and HSTS preload

Once you're on a stable custom domain over HTTPS, consider submitting it to the HSTS preload list so browsers force HTTPS from the first visit.

10. Least-privilege integrations

Vercel integrations (databases, analytics) often request broad scopes. Grant the minimum, and review which integrations have access to your project periodically.

Pre-deploy checklist

  • No secrets behind NEXT_PUBLIC_
  • Secrets scoped per environment
  • Preview deployments protected
  • Security headers in next.config.js and verified live
  • Route handlers/actions enforce auth + authorization
  • Auth enforced at the data layer, not just middleware
  • Cron/webhook endpoints require a secret or signature
  • No production source maps / leaked stack traces

Scan it with Troja

Troja checks your deployed Vercel app for the leaked NEXT_PUBLIC_ secret, missing headers, unprotected API routes, and exposed previews — then hands you a fix prompt for each. Point it at your production URL before your users (or attackers) find the gaps.

Run the scan this post is about.

Free, no signup. See what's hiding inside your walls in ~30 seconds.

Free scan · no signup · results in ~30 seconds
Vercel Deployment Security: The Production Checklist for Next.js — Troja