Vercel Security Best Practices: Headers, Env Vars, Preview Deploys (2026)

In April 2026, a wave of Next.js apps deployed on Vercel were found leaking sensitive credentials through a mistake most developers don't realize they're making: prefixing server-only secrets with NEXT_PUBLIC_. The prefix bundles the variable value into the client-side JavaScript that every visitor downloads. Secrets that were meant to stay on the server ended up in browser network tabs.

The fix is straightforward, but it requires auditing your .env file and your codebase. This guide covers that audit plus the six other Vercel security practices that matter most in 2026.

TL;DR

The fastest wins on Vercel security are: (1) auditing every NEXT_PUBLIC_ variable to confirm it holds no secret, (2) enabling Deployment Protection on preview deploys so they require authentication before anyone can view them, and (3) adding security headers to vercel.json. These three steps address the most common issues found in Vercel deployments and take under 30 minutes combined.

"Vercel secures the infrastructure; you secure the application. Headers, environment variables, and middleware are your responsibility."

Best Practice 1: Add Security Headers 5 min

Security headers protect against common web attacks. Configure them in vercel.json:

vercel.json with security headers
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-XSS-Protection",
          "value": "1; mode=block"
        },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin-when-cross-origin"
        },
        {
          "key": "Permissions-Policy",
          "value": "camera=(), microphone=(), geolocation=()"
        }
      ]
    }
  ]
}

Content Security Policy

Add CSP for additional protection (customize based on your app's needs):

CSP header example
{
  "key": "Content-Security-Policy",
  "value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.yourdomain.com"
}

Best Practice 2: Secure Environment Variables 3 min

Vercel has specific rules for environment variable exposure:

PrefixExposed to Browser?Use For
NEXT_PUBLIC_YesPublic API URLs, feature flags
No prefixNo (server only)API keys, secrets, credentials

Any environment variable starting with NEXT_PUBLIC_ is bundled into your client-side JavaScript. Anyone who visits your site can read it by opening DevTools. One misplaced prefix can expose a Stripe secret key, a Supabase service role key, or a database password to the entire internet.

NEXT_PUBLIC_ leak audit

Run this in your project root before every production deploy:

Audit NEXT_PUBLIC_ references in source
# Find every NEXT_PUBLIC_ variable used in your code
grep -r "NEXT_PUBLIC_" src/ --include="*.ts" --include="*.tsx" | grep "process.env"

# Then check each one in your .env file:
# - Should it ever be read on the server? Remove the prefix.
# - Is it a secret (key, token, password)? It must never have NEXT_PUBLIC_.
Correct environment variable usage
// Safe: Server-side only (no NEXT_PUBLIC_ prefix)
const stripeSecretKey = process.env.STRIPE_SECRET_KEY;
const databaseUrl = process.env.DATABASE_URL;

// Safe: Intentionally public
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const appUrl = process.env.NEXT_PUBLIC_APP_URL;

// WRONG: Secret with public prefix
// const apiSecret = process.env.NEXT_PUBLIC_API_SECRET; // Never do this!

Best Practice 3: Protect Preview Deployments 2 min

By default, every Vercel preview deployment is publicly accessible via its generated URL. That URL is guessable by pattern and gets indexed by bots. Any feature branch you push (including one that contains a half-built auth bypass or a hardcoded test credential) is visible without authentication.

Protect them:

Vercel Authentication

  1. Go to Project Settings in Vercel Dashboard
  2. Navigate to Deployment Protection
  3. Enable Vercel Authentication for Preview deployments
  4. Configure which team members can access previews

Password Protection

For additional protection, add password authentication to previews:

Deployment Protection settings
// In Vercel Dashboard > Settings > Deployment Protection
{
  "protection": {
    "preview": {
      "enabled": true,
      "password": true  // Requires password for access
    }
  }
}

Best Practice 4: Use Edge Middleware for Auth 15 min

Protect routes at the edge before they reach your application:

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Check for auth token
  const token = request.cookies.get('auth-token');

  // Protected routes
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }

  // Admin routes
  if (request.nextUrl.pathname.startsWith('/admin')) {
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
    // Additional admin check would go here
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*', '/api/protected/:path*']
};

Best Practice 5: Secure API Routes 15 min

Vercel API routes (or Next.js route handlers) need proper security:

Secure API route pattern
// app/api/user/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyAuth } from '@/lib/auth';

export async function GET(request: NextRequest) {
  // Verify authentication
  const authResult = await verifyAuth(request);
  if (!authResult.success) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  // Rate limiting check
  const rateLimitResult = await checkRateLimit(authResult.userId);
  if (!rateLimitResult.allowed) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { status: 429 }
    );
  }

  // Proceed with authenticated request
  const userData = await getUserData(authResult.userId);
  return NextResponse.json(userData);
}

export async function POST(request: NextRequest) {
  const authResult = await verifyAuth(request);
  if (!authResult.success) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Validate input
  const body = await request.json();
  const validation = validateUserUpdate(body);
  if (!validation.success) {
    return NextResponse.json(
      { error: 'Invalid input', details: validation.errors },
      { status: 400 }
    );
  }

  // Process request...
}

Best Practice 6: Configure CORS Properly 10 min

For API routes that need CORS, configure it explicitly:

CORS configuration in API route
// app/api/public/route.ts
import { NextRequest, NextResponse } from 'next/server';

const allowedOrigins = [
  'https://yourdomain.com',
  'https://app.yourdomain.com',
];

export async function OPTIONS(request: NextRequest) {
  const origin = request.headers.get('origin');

  if (origin && allowedOrigins.includes(origin)) {
    return new NextResponse(null, {
      status: 204,
      headers: {
        'Access-Control-Allow-Origin': origin,
        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
        'Access-Control-Max-Age': '86400',
      },
    });
  }

  return new NextResponse(null, { status: 403 });
}

export async function GET(request: NextRequest) {
  const origin = request.headers.get('origin');
  const response = NextResponse.json({ data: 'example' });

  if (origin && allowedOrigins.includes(origin)) {
    response.headers.set('Access-Control-Allow-Origin', origin);
  }

  return response;
}

Best Practice 7: Monitor and Alert 5 min

Set up monitoring for your Vercel deployment:

  • Vercel Analytics: Monitor performance and errors
  • Log Drains: Send logs to external monitoring services
  • Status Alerts: Get notified of deployment issues
  • Speed Insights: Track Core Web Vitals

Vercel Security Checklist:

  • Security headers configured in vercel.json
  • No secrets in NEXT_PUBLIC_ environment variables
  • Preview deployments protected with authentication
  • Edge Middleware protecting sensitive routes
  • API routes validate authentication and input
  • CORS configured with specific allowed origins
  • Monitoring and alerts enabled

Official Resources: For the latest information, see Vercel Security Documentation, Vercel Headers Configuration, and Deployment Protection Guide.

Is Vercel secure by default?

Vercel provides a secure infrastructure with automatic HTTPS, DDoS protection, and isolated builds. However, application-level security like headers, environment variable handling, and authentication is your responsibility to configure. The platform handles the server; what runs on it is up to you.

How do I audit my app for NEXT_PUBLIC_ leaks?

Run grep -r "NEXT_PUBLIC_" src/ --include="*.ts" --include="*.tsx" in your project root to find every variable your code exposes to the browser. Open your .env file side by side and verify none of those variables hold API keys, tokens, or passwords. If they do, remove the NEXT_PUBLIC_ prefix and move reads to a server component or API route.

Are environment variables safe on Vercel?

Server-side environment variables (without NEXT_PUBLIC_ prefix) are encrypted and only available during build and runtime on the server. Variables with NEXT_PUBLIC_ prefix are bundled into your client-side JavaScript, visible to any user who opens DevTools. The prefix is the only thing separating a safe public URL from an exposed secret key.

Should I protect preview deployments?

Yes. Every preview deployment is publicly accessible by default, indexed by bots, and shares your production environment variables unless you configure separate preview env vars. Enable Vercel Authentication under Project Settings > Deployment Protection so only logged-in team members can view preview URLs.

How do I protect API routes on Vercel?

Use Edge Middleware for route-level protection, implement authentication checks in each API route, add rate limiting, and validate all input. Never trust client-side data without server-side validation.

Further Reading

Put these practices into action with our step-by-step guides.

Verify Your Vercel Security

Scan your Vercel deployment for security headers and configuration issues.

Best Practices

Vercel Security Best Practices: Headers, Env Vars, Preview Deploys (2026)