Vercel
intermediate
25-30 minutes

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:

FrameworkOutput Directory
Next.js.next (handled automatically)
Vitedist
Create React Appbuild
Astrodist
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

CodeTypeUse Case
301permanent: trueURL permanently moved
302permanent: falseTemporary redirect
307TemporaryPreserve HTTP method
308PermanentPreserve 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
}
FileURL
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, 3008 MB

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:

  1. beforeFiles rewrites
  2. Filesystem (check if file exists)
  3. rewrites
  4. redirects
  5. headers
  6. afterFiles rewrites
Note

Checkpoint: Create a vercel.json with at least one redirect and security headers, then deploy to verify they're working.