You have deployed to Vercel. It is live. But is it secure?
Vercel makes deployment effortless. Push to main, get a production URL in under a minute. That speed is genuinely impressive, but it also means security decisions that used to happen during a manual deployment process now happen (or don't happen) automatically. The defaults are reasonable, not bulletproof.
We scan thousands of Vercel-hosted Next.js apps at CheckVibe. The same security gaps appear repeatedly. Not because developers are careless, but because Vercel's deployment experience is so smooth that the security configuration step gets skipped entirely.
Here is the 10-point checklist we recommend for every Next.js app running on Vercel.
The 10-Point Vercel Deployment Security Checklist
1. Environment Variables: Never Prefix Secrets with NEXT_PUBLIC_
This is the single most common security mistake in Next.js applications, and Vercel's environment variable UI makes it easy to overlook. Any variable prefixed with NEXT_PUBLIC_ is bundled into client-side JavaScript and shipped to every visitor's browser.
Dangerous:
NEXT_PUBLIC_DATABASE_URL=postgresql://user:password@host/db
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_abc123...
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi...
Every one of those values is now visible in the browser's developer tools. Anyone can extract them from your JavaScript bundle.
Correct approach:
# Client-safe (public keys only)
NEXT_PUBLIC_SUPABASE_URL=https://xyz.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOi...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
# Server-only (no NEXT_PUBLIC_ prefix)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi...
STRIPE_SECRET_KEY=sk_live_abc123...
DATABASE_URL=postgresql://user:password@host/db
Store server-only variables in the Vercel dashboard under Project Settings > Environment Variables. Do not commit them to .env files in your repository. Set the scope (Production, Preview, Development) appropriately so staging environments do not use production credentials.
Rule: If a key grants write access, admin privileges, or bypasses security policies, it must never carry the NEXT_PUBLIC_ prefix. For more on this pattern, see our guide on Next.js security best practices.
2. Security Headers in next.config.ts
Vercel does not add security headers by default. Without explicit configuration, your Next.js app ships with no Content-Security-Policy, no HSTS preload, no clickjacking protection, and no referrer controls. You need to set these yourself.
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self' https://*.supabase.co https://api.stripe.com",
"frame-ancestors 'none'",
].join('; '),
},
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
],
},
];
},
};
export default nextConfig;
Adjust the connect-src directive to include your actual API domains. If you use analytics or third-party scripts, add those origins to the appropriate directives. The goal is to be as restrictive as possible while keeping your app functional.
For a deep dive on each header, see our complete security headers guide.
3. Deployment Protection for Preview and Staging
Every pull request on Vercel generates a unique preview deployment URL. By default, these are publicly accessible. If your preview environments connect to production databases or contain unreleased features, anyone with the URL can access them.
Enable deployment protection:
- Go to Project Settings > Deployment Protection in Vercel
- Enable Vercel Authentication for preview deployments (requires a Vercel account to access)
- For sensitive staging environments, enable Password Protection as an additional layer
- Consider enabling Protection Bypass for Automation only if CI/CD tools need access, and use short-lived tokens
Preview URLs follow a predictable pattern (project-name-git-branch-name-team.vercel.app), which means they are discoverable. Do not assume obscurity protects them.
4. Server Actions Security: Validate Inputs and Check Auth
Next.js Server Actions are convenient. They look like function calls. They are actually POST endpoints. Every Server Action is an HTTP endpoint that can be called directly with fetch or curl, bypassing your UI entirely.
Vulnerable:
// app/actions.ts
'use server';
export async function deleteUser(userId: string) {
await db.query('DELETE FROM users WHERE id = $1', [userId]);
}
Anyone can call this action with a forged request. There is no authentication, no authorization, and no input validation.
Fixed:
// app/actions.ts
'use server';
import { createClient } from '@/lib/supabase/server';
import { z } from 'zod';
const deleteUserSchema = z.object({
userId: z.string().uuid(),
});
export async function deleteUser(userId: string) {
const parsed = deleteUserSchema.safeParse({ userId });
if (!parsed.success) {
throw new Error('Invalid input');
}
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
throw new Error('Unauthorized');
}
// Check admin role
const { data: profile } = await supabase
.from('profiles')
.select('role')
.eq('id', user.id)
.single();
if (profile?.role !== 'admin') {
throw new Error('Forbidden');
}
await db.query('DELETE FROM users WHERE id = $1', [parsed.data.userId]);
}
Rule: Treat every Server Action exactly like an API route. Validate all inputs with a schema library. Check authentication and authorization on every call.
5. API Route Authentication: No Public Mutation Endpoints
Every file in app/api/ is a publicly accessible HTTP endpoint. Next.js does not enforce any authentication by default. This means every route that reads or writes data must verify the caller's identity.
// app/api/projects/route.ts
import { createClient } from '@/lib/supabase/server';
export async function POST(request: Request) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = await request.json();
// Validate and sanitize input before processing
// ... your logic here
return Response.json({ success: true });
}
Do not rely on client-side routing to "hide" API routes. Bots and attackers will discover them through JavaScript bundle analysis, brute-force path scanning, or error messages that leak route structures.
6. Edge Middleware for Auth: Protect Routes Before They Render
Middleware in Next.js runs at the edge before any page or API route is reached. Use it to enforce authentication on protected routes, redirect unauthenticated users, and apply rate limiting.
// middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
let response = NextResponse.next({
request: { headers: request.headers },
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
response.cookies.set(name, value, options);
});
},
},
}
);
const { data: { user } } = await supabase.auth.getUser();
if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
const redirectUrl = request.nextUrl.clone();
redirectUrl.pathname = '/login';
return NextResponse.redirect(redirectUrl);
}
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/api/projects/:path*', '/api/keys/:path*'],
};
The matcher configuration is critical. Without it, middleware runs on every request including static assets, which hurts performance. Be explicit about which paths need protection.
7. CORS Configuration: Do Not Use Wildcards in Production
Using Access-Control-Allow-Origin: * in production means any website on the internet can make authenticated requests to your API from a user's browser.
// app/api/data/route.ts
const ALLOWED_ORIGINS = [
'https://yourdomain.com',
'https://app.yourdomain.com',
];
export async function GET(request: Request) {
const origin = request.headers.get('origin') ?? '';
if (!ALLOWED_ORIGINS.includes(origin)) {
return new Response('Forbidden', { status: 403 });
}
const data = { /* ... */ };
return Response.json(data, {
headers: {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
Be explicit about which origins can access your API. If you have multiple subdomains, list them individually rather than using a broad regex that could match unintended domains. For background on CORS misconfigurations and their impact, see our CORS misconfiguration guide.
8. Vercel WAF and DDoS Protection: Know What Is Covered
Vercel provides built-in DDoS protection and, on Pro and Enterprise plans, a Web Application Firewall (WAF). Understanding the boundary of these protections is important.
What Vercel handles automatically:
- Volumetric DDoS attacks (network-layer flooding)
- Bot detection and challenge pages
- Basic rate limiting at the edge
What Vercel does not handle for you:
- Application-layer attacks (SQL injection, XSS payloads in form data)
- Business logic abuse (credential stuffing, API scraping)
- Input validation and sanitization
- Authentication and authorization
Vercel's WAF rules (available on Pro plans) let you block traffic by IP, country, path pattern, and request header. Use them to restrict access to admin routes, block known bad IPs, and limit traffic to specific paths.
Do not assume the WAF replaces application-level security. It is a complementary layer, not a substitute for validating inputs and checking auth in your code.
9. Source Maps and Logs Protection
By default, Next.js generates source maps in production builds. If these are exposed, attackers can read your original source code, understand your application logic, and find vulnerabilities more easily.
Disable source maps in production:
// next.config.ts
const nextConfig: NextConfig = {
productionBrowserSourceMaps: false,
// ... other config
};
In the Vercel dashboard, check Project Settings > Security for options related to:
- Source Protection prevents direct access to deployment source
- Log Drains ensure sensitive data in logs is routed to secure destinations
- Function Logs may contain request bodies and error details; avoid logging secrets
Also verify that your error handling does not expose stack traces or internal paths to end users. Use generic error messages in production responses.
10. Git Fork Protection: Prevent Unauthorized Deployments
If your repository is public or accepts contributions from external forks, Vercel will build and deploy preview environments for forked pull requests by default. This means an attacker could submit a PR that modifies your build configuration, injects malicious code, or exfiltrates environment variables during the build step.
Mitigate this:
- Go to Project Settings > Git in Vercel
- Disable Build for Fork PRs or enable Require Approval for fork deployments
- Never expose production environment variables to preview deployments from forks
- Review Vercel's environment variable scoping: set secrets to "Production" scope only
For open-source projects, this is especially important. A forked PR can run arbitrary code during npm run build, which has access to any environment variables scoped to preview deployments.
Common Vercel Security Mistakes
Beyond the checklist, these are the mistakes we see most often in production Vercel deployments.
Exposing secrets via NEXT_PUBLIC_ variables. This shows up in roughly 1 in 5 scans we run on Next.js apps. Developers copy environment variable configurations between projects and forget to remove the prefix from sensitive keys.
No Content-Security-Policy header. Without CSP, your app has no browser-enforced protection against XSS. If an attacker finds a way to inject a script tag, nothing stops it from executing.
Preview deployments accessible to the public. Preview URLs are not secret. They follow predictable naming patterns and are often indexed by search engines or shared in public Slack channels and GitHub comments.
Source maps shipped in production. These give attackers a complete map of your application code. Combined with other information, they make vulnerability discovery significantly faster.
Wildcard CORS in API routes. Using Access-Control-Allow-Origin: * on routes that return user data or accept mutations lets any website make requests on behalf of your users.
Missing auth on Server Actions. Because Server Actions look like regular function calls in your component code, it is easy to forget that they are exposed HTTP endpoints that anyone can invoke.
Vercel-Specific Security Features Worth Enabling
Vercel offers several security features beyond basic hosting. Not all are available on every plan, but they are worth knowing about.
Web Application Firewall (WAF): Available on Pro and Enterprise plans. Configure rules to block traffic by IP, geography, path, or header. Useful for restricting admin panels and blocking abusive traffic patterns.
DDoS Protection: Included on all plans. Vercel's edge network absorbs volumetric attacks automatically. For application-layer protection, you still need rate limiting in your code.
Deployment Protection: Password protection and Vercel Authentication for preview and staging environments. Essential for any project where preview deployments touch real data.
OIDC Federation: For Enterprise plans, Vercel supports OpenID Connect federation with identity providers. This lets you control who can deploy and access project settings through your existing SSO infrastructure.
Attack Challenge Mode: When under active attack, Vercel can automatically present challenge pages to suspicious traffic. This is managed through the dashboard or API.
Secure Compute: Enterprise feature that runs serverless functions in dedicated, isolated environments. Relevant for apps handling regulated data (healthcare, finance).
Note that Vercel Speed Insights and Web Analytics are performance tools, not security features. They measure Core Web Vitals and visitor metrics, but they do not detect or prevent attacks.
How CheckVibe Scans Vercel Deployments
CheckVibe runs 36 automated security checks against any publicly accessible website, including Vercel-hosted Next.js apps. Our scanner suite includes a dedicated Vercel scanner that checks for platform-specific issues.
Here is what we detect on Vercel deployments:
Security headers analysis. We check for the presence and correct configuration of CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy. Missing or misconfigured headers are flagged with specific remediation steps.
Exposed secrets detection. Our API key leak scanner examines your client-side JavaScript bundle for patterns matching common API keys, database connection strings, and tokens. If a NEXT_PUBLIC_ variable contains something that looks like a secret key, we flag it.
CORS misconfiguration. We test your API endpoints for overly permissive CORS headers. Wildcard origins, missing credentials handling, and other CORS issues are identified and reported.
Technology fingerprinting. We identify that your app runs on Vercel and Next.js, then apply platform-specific security checks. The scanner knows which default configurations are insecure for your particular stack.
SSL and certificate analysis. We verify your TLS configuration, certificate chain, and HSTS setup. While Vercel handles certificates automatically, misconfigurations can still occur with custom domains.
Site crawling for comprehensive coverage. Our crawler discovers pages and API endpoints across your site, ensuring that security checks run against your entire attack surface, not just the homepage.
You can run a free scan on your Vercel deployment right now and see exactly which items from this checklist your app passes or fails.
Frequently Asked Questions
Does Vercel handle security for me?
Partially. Vercel handles infrastructure security: TLS certificates, DDoS protection, network isolation, and edge caching. It does not handle application security: authentication, authorization, input validation, security headers, CORS, or secret management. Those are your responsibility as the developer. Think of Vercel as a secure building — it has locks on the front door, but you still need to lock your apartment.
Are Vercel preview deployments secure?
Not by default. Preview deployments are publicly accessible via predictable URLs. Enable Deployment Protection in your Vercel project settings to require authentication for preview URLs. Never scope production secrets (database credentials, API keys) to preview environments.
Do I need a WAF with Vercel?
For most applications, Vercel's built-in DDoS protection combined with proper application-level security is sufficient. If you are handling sensitive data, processing payments, or facing targeted attacks, the Vercel WAF (Pro plan) adds useful IP-based and path-based blocking rules. It is not a replacement for input validation and auth checks in your code.
How do I add security headers on Vercel?
Configure them in next.config.ts using the headers() function as shown in checklist item #2. You can also add headers via vercel.json, but the next.config.ts approach is more maintainable and keeps your security configuration in version control alongside your application code.
Can attackers read my source code on Vercel?
If source maps are enabled in production (which is the default), attackers can reconstruct your original source code from the browser's developer tools. Set productionBrowserSourceMaps: false in next.config.ts. Additionally, enable Source Protection in Vercel's dashboard settings to prevent direct access to your deployment's source files.
Should I use Vercel's edge middleware for rate limiting?
Edge middleware is a good location for basic rate limiting because it runs before your application code. However, Vercel's edge runtime has limitations on execution time and available APIs. For sophisticated rate limiting (sliding windows, per-user limits, tiered thresholds), consider a dedicated rate limiting service or implement it in your API routes with a backing store like Redis.
Start Securing Your Vercel Deployment
Vercel is an excellent platform for deploying Next.js apps. But deploying is step one. Securing what you deployed is step two, and it does not happen automatically.
Work through this checklist from top to bottom. The first three items — environment variables, security headers, and deployment protection — will address the most common vulnerabilities we see. Then tackle auth, CORS, and the remaining items.
If you want to know exactly where your Vercel deployment stands right now, run a free CheckVibe scan. In under 60 seconds, you will get a full security report covering headers, exposed secrets, CORS, SSL, and 30+ additional checks tailored to your stack.