TL;DR
Vercel handles SSL, DDoS protection, and infrastructure security for you. Your job is managing environment variables correctly (NEXT_PUBLIC_ prefix exposes to browser), securing API routes with authentication, and configuring security headers. Use Vercel's preview deployments carefully since they share the same environment variables as production by default.
What Vercel Handles for You
Vercel provides several security features automatically:
- SSL/HTTPS: All deployments get automatic SSL certificates
- DDoS protection: Built-in protection against denial of service attacks
- Edge network: Requests are handled at the edge, reducing attack surface
- Isolated functions: Serverless functions run in isolated environments
- Automatic updates: Infrastructure is maintained and patched by Vercel
Environment Variables: The Most Common Mistake
Vercel's environment variable handling is where most security issues occur. Understanding the naming convention is critical:
| Variable Name | Where It's Available | Safe for Secrets? |
|---|---|---|
| DATABASE_URL | Server-side only | Yes |
| NEXT_PUBLIC_API_URL | Browser + Server | No (public) |
| STRIPE_SECRET_KEY | Server-side only | Yes |
| NEXT_PUBLIC_STRIPE_KEY | Browser + Server | Only publishable keys |
Critical rule: Any variable starting with NEXT_PUBLIC_ is bundled into your frontend JavaScript and visible to anyone. Never put secrets in NEXT_PUBLIC_ variables.
Common Environment Variable Mistakes
# This exposes your database password to the browser!
NEXT_PUBLIC_DATABASE_URL=postgres://user:password@host/db
# This exposes your secret API key!
NEXT_PUBLIC_OPENAI_KEY=sk-abc123...
# Server-side only (safe)
DATABASE_URL=postgres://user:password@host/db
OPENAI_API_KEY=sk-abc123...
# Public variables (only non-secrets)
NEXT_PUBLIC_APP_URL=https://myapp.com
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
Securing API Routes
API routes in Next.js on Vercel are serverless functions. They need authentication:
// pages/api/users.js or app/api/users/route.js
export async function GET() {
// BAD: Anyone can access this
const users = await db.users.findMany();
return Response.json(users);
}
// app/api/users/route.js
import { getServerSession } from 'next-auth';
export async function GET(request) {
// Check authentication
const session = await getServerSession();
if (!session) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// Check authorization (e.g., admin only)
if (session.user.role !== 'admin') {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
const users = await db.users.findMany();
return Response.json(users);
}
Security Headers Configuration
Add security headers in your next.config.js or vercel.json:
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
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: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';",
},
],
},
];
},
};
module.exports = nextConfig;
Preview Deployments Security
Vercel creates preview deployments for every git branch and PR. Security considerations:
- Preview deployments share environment variables with production by default
- Preview URLs are public (though not easily guessable)
- Consider using different environment variables for preview vs production
Protecting Preview Deployments
Preview Deployment Security Options
Enable Vercel Authentication for previews (Team/Enterprise)
Use different API keys for preview environment
Don't put sensitive test data in preview databases
Consider password protecting preview deployments
Serverless Function Security
Input Validation
import { z } from 'zod';
const UserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
export async function POST(request) {
const body = await request.json();
// Validate input
const result = UserSchema.safeParse(body);
if (!result.success) {
return Response.json(
{ error: 'Invalid input', details: result.error.issues },
{ status: 400 }
);
}
// Use validated data
const { email, name } = result.data;
// ... process request
}
Rate Limiting
import { kv } from '@vercel/kv';
export async function POST(request) {
const ip = request.headers.get('x-forwarded-for') || 'unknown';
const key = `ratelimit:${ip}`;
// Get current count
const count = await kv.incr(key);
// Set expiry on first request
if (count === 1) {
await kv.expire(key, 60); // 60 second window
}
// Check limit
if (count > 10) { // 10 requests per minute
return Response.json(
{ error: 'Rate limit exceeded' },
{ status: 429 }
);
}
// Process request...
}
Vercel Security Checklist
Before Going Live
No secrets in NEXT_PUBLIC_ variables
All API routes have authentication checks
Security headers configured
Input validation on all endpoints
Rate limiting on sensitive endpoints
Preview deployments are protected or use test data
Environment variables are scoped correctly
Logs don't contain sensitive data
CORS configured appropriately
Are my environment variables encrypted on Vercel?
Yes, Vercel encrypts environment variables at rest and in transit. They're securely injected into your functions at runtime. However, variables with the NEXT_PUBLIC_ prefix are bundled into your JavaScript and sent to browsers.
Can someone access my serverless function code?
No, your serverless function source code is not accessible to users. They can only interact with your functions through HTTP requests. The code runs in Vercel's secure infrastructure.
Should I worry about preview deployment URLs being discovered?
Preview URLs are random and hard to guess, but they are technically public. For sensitive projects, enable Vercel Authentication (available on Team/Enterprise plans) or use a password protection solution.
How do I rotate a compromised secret on Vercel?
Update the environment variable in Vercel Dashboard, then redeploy your application. The new value will be used immediately. Also rotate the credential with the original service (e.g., generate a new API key with your provider).