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:
{
"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):
{
"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:
| Prefix | Exposed to Browser? | Use For |
|---|---|---|
| NEXT_PUBLIC_ | Yes | Public API URLs, feature flags |
| No prefix | No (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:
# 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_.
// 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
- Go to Project Settings in Vercel Dashboard
- Navigate to Deployment Protection
- Enable Vercel Authentication for Preview deployments
- Configure which team members can access previews
Password Protection
For additional protection, add password authentication to previews:
// 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:
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:
// 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:
// 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.