Project Configuration (vercel.json)
Configure builds, redirects, headers, and rewrites with vercel.json for complete control over your deployment.
What you'll accomplish
Custom-configured Vercel project with redirects, headers, and optimized builds
Prerequisites
- Project deployed to Vercel
What is vercel.json?
vercel.json is a configuration file in your project root that controls how Vercel builds and serves your application. It's optional - Vercel auto-detects most settings - but powerful for customization.
json{ "buildCommand": "npm run build", "outputDirectory": "dist", "framework": "nextjs" }
Build Configuration
Build Command
Override the default build command:
json{ "buildCommand": "npm run build:production" }
Install Command
Customize dependency installation:
json{ "installCommand": "pnpm install --frozen-lockfile" }
Output Directory
Specify where built files live:
json{ "outputDirectory": "dist" }
Common output directories by framework:
| Framework | Output Directory |
|---|---|
| Next.js | .next (handled automatically) |
| Vite | dist |
| Create React App | build |
| Astro | dist |
| SvelteKit | .svelte-kit |
Node.js Version
json{ "functions": { "api/**/*.js": { "runtime": "nodejs20.x" } } }
Or set globally via Vercel dashboard: Project Settings → General → Node.js Version.
Framework Selection
json{ "framework": "nextjs" }
Options: nextjs, gatsby, create-react-app, vue, nuxt, svelte, angular, ember, hugo, jekyll, hexo, docusaurus, astro, remix, and more.
Regions
Deploy to Specific Regions
json{ "regions": ["iad1", "sfo1", "hnd1"] }
Common regions:
iad1- Washington, D.C. (US East)sfo1- San Francisco (US West)cdg1- Paris (Europe)hnd1- Tokyo (Asia)syd1- Sydney (Oceania)
Note
By default, Vercel deploys to all regions for global performance. Only restrict regions if you have compliance requirements.
Redirects
Simple Redirects
json{ "redirects": [ { "source": "/old-page", "destination": "/new-page", "permanent": true }, { "source": "/blog/:slug", "destination": "/posts/:slug", "permanent": true } ] }
Redirect Types
| Code | Type | Use Case |
|---|---|---|
| 301 | permanent: true | URL permanently moved |
| 302 | permanent: false | Temporary redirect |
| 307 | Temporary | Preserve HTTP method |
| 308 | Permanent | Preserve HTTP method |
External Redirects
json{ "redirects": [ { "source": "/twitter", "destination": "https://twitter.com/yourhandle", "permanent": false } ] }
Conditional Redirects
json{ "redirects": [ { "source": "/api/:path*", "destination": "https://api.example.com/:path*", "has": [ { "type": "header", "key": "x-redirect-api" } ] } ] }
Regex Redirects
json{ "redirects": [ { "source": "/blog/(.*)", "destination": "/posts/$1", "permanent": true } ] }
Rewrites
Rewrites serve content from a different path without changing the URL (unlike redirects).
Basic Rewrite
json{ "rewrites": [ { "source": "/api/:path*", "destination": "https://api.example.com/:path*" } ] }
SPA Fallback
For single-page apps, route all paths to index.html:
json{ "rewrites": [ { "source": "/(.*)", "destination": "/index.html" } ] }
Proxy to Backend
json{ "rewrites": [ { "source": "/api/:path*", "destination": "https://backend.example.com/:path*" } ] }
This lets you:
- Hide your backend URL
- Avoid CORS issues
- Use the same domain for frontend and API
Conditional Rewrites
json{ "rewrites": [ { "source": "/feature/:path*", "destination": "/v2/:path*", "has": [ { "type": "cookie", "key": "beta", "value": "true" } ] } ] }
Headers
Custom Response Headers
json{ "headers": [ { "source": "/(.*)", "headers": [ { "key": "X-Frame-Options", "value": "DENY" }, { "key": "X-Content-Type-Options", "value": "nosniff" } ] } ] }
Security Headers (Recommended)
json{ "headers": [ { "source": "/(.*)", "headers": [ { "key": "X-DNS-Prefetch-Control", "value": "on" }, { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" }, { "key": "X-XSS-Protection", "value": "1; mode=block" }, { "key": "X-Frame-Options", "value": "SAMEORIGIN" }, { "key": "X-Content-Type-Options", "value": "nosniff" }, { "key": "Referrer-Policy", "value": "origin-when-cross-origin" } ] } ] }
CORS Headers
json{ "headers": [ { "source": "/api/(.*)", "headers": [ { "key": "Access-Control-Allow-Origin", "value": "*" }, { "key": "Access-Control-Allow-Methods", "value": "GET, POST, PUT, DELETE, OPTIONS" }, { "key": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" } ] } ] }
Cache Control
json{ "headers": [ { "source": "/static/(.*)", "headers": [ { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] }, { "source": "/(.*)", "headers": [ { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" } ] } ] }
Clean URLs
Remove .html extensions from URLs:
json{ "cleanUrls": true }
| File | URL |
|---|---|
about.html | /about |
blog/post.html | /blog/post |
Trailing Slashes
Control trailing slashes in URLs:
json{ "trailingSlash": false }
true-/about/(with slash)false-/about(no slash)
Functions Configuration
Runtime and Memory
json{ "functions": { "api/**/*.js": { "runtime": "nodejs20.x", "memory": 1024, "maxDuration": 10 }, "api/heavy-task.js": { "memory": 3008, "maxDuration": 60 } } }
Memory Options
128,256,512,1024,2048,3008MB
Max Duration
- Hobby: up to 10 seconds
- Pro: up to 60 seconds
- Enterprise: up to 900 seconds
Git Configuration
Ignored Build Step
Skip builds for certain branches:
json{ "git": { "deploymentEnabled": { "main": true, "preview": true, "staging": false } } }
Silent GitHub Comments
json{ "github": { "silent": true } }
Complete Example
Here's a comprehensive vercel.json example:
json{ "buildCommand": "npm run build", "outputDirectory": "dist", "installCommand": "npm ci", "framework": "vite", "regions": ["iad1"], "cleanUrls": true, "trailingSlash": false, "headers": [ { "source": "/(.*)", "headers": [ { "key": "X-Frame-Options", "value": "DENY" }, { "key": "X-Content-Type-Options", "value": "nosniff" } ] }, { "source": "/static/(.*)", "headers": [ { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] } ], "redirects": [ { "source": "/old", "destination": "/new", "permanent": true }, { "source": "/docs", "destination": "https://docs.example.com", "permanent": false } ], "rewrites": [ { "source": "/api/:path*", "destination": "https://api.example.com/:path*" }, { "source": "/(.*)", "destination": "/index.html" } ], "functions": { "api/**/*.js": { "runtime": "nodejs20.x", "memory": 1024, "maxDuration": 10 } } }
Validation
Test your configuration before deploying:
bash# Vercel validates on deploy vercel --debug # Or use a JSON schema validator npx ajv-cli validate -s vercel-json-schema.json -d vercel.json
Order of Precedence
When multiple rules match, Vercel applies them in this order:
beforeFilesrewrites- Filesystem (check if file exists)
rewritesredirectsheadersafterFilesrewrites
Note
Checkpoint: Create a vercel.json with at least one redirect and security headers, then deploy to verify they're working.