Gerson

Gerson

Passionate developer specializing in web development, cloud architecture, and system design.

TypeScriptReactNext.jsPythonFastAPISQLNode.jsAWS

Typed Project Config: Migrating vercel.json to vercel.ts

vercel.ts replaces vercel.json with full TypeScript types, dynamic logic, and environment access. A practical migration walkthrough — rewrites, headers, crons, and queues — plus what stays in package.json.

Developer editing a TypeScript file with the editor open

If you have been copy-pasting regex into vercel.json for years, 2026 brings a small but welcome upgrade: vercel.ts is now the recommended way to configure a Vercel project. You get real TypeScript types, helper functions for common patterns, and the ability to branch on environment variables — all of which JSON could only approximate.

This post walks through migrating a real vercel.json to vercel.ts, covers the patterns that get noticeably better, and flags a few things that do not belong in this file at all.

Install and Setup

Terminal

pnpm add -D @vercel/config
rm vercel.json
touch vercel.ts

vercel.ts

import { routes, deploymentEnv, type VercelConfig } from '@vercel/config/v1';

export const config: VercelConfig = {
  framework: 'nextjs',
  regions: ['iad1', 'sfo1'],
};

The CLI picks up vercel.ts automatically. If both files exist, vercel.ts wins, so you can migrate one section at a time.

Rewrites, Redirects, and Headers

The routes helper exposes typed builders for the common cases. Each one returns the same shape the JSON config expected, so the generated output is stable.

vercel.ts — routes

import { routes, type VercelConfig } from '@vercel/config/v1';

export const config: VercelConfig = {
  framework: 'nextjs',
  rewrites: [
    routes.rewrite('/api/(.*)', 'https://api.example.com/$1'),
    routes.rewrite('/blog/:slug', '/articles/:slug'),
  ],
  redirects: [
    routes.redirect('/old-docs', '/docs', { permanent: true }),
    routes.redirect('/twitter', 'https://x.com/acme'),
  ],
  headers: [
    routes.cacheControl('/static/(.*)', {
      public: true,
      maxAge: '1 week',
      immutable: true,
    }),
    routes.securityHeaders('/(.*)'),
  ],
};

Notice routes.securityHeaders(): a batteries-included helper that emits a sensible Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, and friends. The previous approach was to copy headers from a blog post and get them slightly wrong.

Branching on Environment

The deploymentEnv helper is the reason most teams actually migrate. You can branch the config on whether the deployment is production, preview, or development.

vercel.ts — env-aware config

import { routes, deploymentEnv, type VercelConfig } from '@vercel/config/v1';

const isProd = deploymentEnv === 'production';

export const config: VercelConfig = {
  framework: 'nextjs',
  headers: [
    routes.header('/(.*)', 'X-Robots-Tag', isProd ? 'all' : 'noindex'),
  ],
  crons: isProd
    ? [{ path: '/api/cleanup', schedule: '0 3 * * *' }]
    : [],
};

The practical wins: preview deployments no longer get indexed by Google, and you do not accidentally run cron jobs on every preview branch. Both of these were possible with vercel.json only by using env vars and conditionals in the hosted dashboard — which nothing on disk reflected.

Crons and Queues in One Place

Crons have moved from the dashboard into code for years; Queues are the newer addition. Co-locating both with the rest of the config keeps infra declarative and reviewable.

vercel.ts — crons and queues

export const config: VercelConfig = {
  framework: 'nextjs',
  crons: [
    { path: '/api/cron/digest', schedule: '0 8 * * MON' },
    { path: '/api/cron/cleanup', schedule: '0 3 * * *' },
  ],
  queues: [
    {
      name: 'emails',
      consumer: '/api/queues/emails',
      maxRetries: 3,
      maxConcurrency: 20,
    },
  ],
};

Pro Tip: Wrap complex rewrite sets in plain functions and as const arrays. TypeScript will catch typos in route patterns at compile time instead of at 3am when a redirect loop takes down production.

What Does Not Go In vercel.ts

  • Environment variables — use vercel env or the dashboard. Secrets should never live in a committed file.
  • Per-function runtime config (memory, maxDuration, runtime) — keep that in the route file's export const declarations so it lives next to the code it affects.
  • Feature flags — use a flag service or the Vercel Flags SDK, not a deploymentEnv branch in config.

Running the Migration

  1. Install @vercel/config as a dev dependency
  2. Copy vercel.json contents into vercel.ts using the routes helpers — TypeScript will flag shape mismatches
  3. Run vercel build locally; inspect .vercel/output/config.json to confirm the generated output matches the old JSON
  4. Delete vercel.json
  5. Commit and deploy a preview to verify routing and headers behave identically

Resources