# Managing Redirects from your CMS using Vercel Bulk Redirects

**Author:** Pranav Karthik, Ismael Rumzan

---

Your e-commerce site needs `/catalog/fall` to point to different collections each year. Marketing runs campaigns with vanity URLs that need to update destinations weekly. Product SKUs get retired, but inbound links from blog posts and emails still need to work.

Currently, you either update redirects in middleware code (which needs developer resources for every change and adds additional computation at request time) or query your CMS at runtime (which adds latency to requests). Vercel bulk redirects solves this with:

1. **Build-time execution** - Fetch redirects from your CMS during deployment, not on every request
   
2. **CDN-level handling** - Redirects happen at Vercel’s CDN level in 5-10ms, before your application code runs
   

In this guide, you will implement bulk redirects by configuring `vercel.ts`. Redirects can also be managed via the [SDK/API](#other-ways-to-manage-redirects) if the redirects need to be updated without waiting for a new deployment — this option is covered later in the guide.

## Overview

- Understanding bulk redirects with Vercel configuration
  
- Hands-on setup with the `cms-bulk-redirects` example
  
- Adapting the pattern to any CMS (Sanity, Strapi, etc.)
  

## Understanding bulk redirects with Vercel configuration

The deployment workflow with `vercel.ts` is as follows:

1. The developer pushes code (or the CMS webhook triggers a deployment)
   
2. The build process starts and runs `vercel.ts`
   
3. `vercel.ts` fetches all redirect rules from your CMS
   
4. The CMS returns redirect data
   
5. `vercel.ts` generates a `generated-redirects.json` file
   
6. Vercel publishes a deployment with redirects to CDN
   

> Redirects now exist as static configuration in the CDN. When a user visits a path, Vercel’s CDN checks the redirect configuration and if there is a match, it redirects instantly

### Updating redirects

Build-time means you need to redeploy when redirects change. You can automate this by setting up a webhook in your CMS that triggers a Vercel deployment when redirect entries change. When your marketing team edits a redirect in your CMS and saves it, a new deployment starts automatically. We'll cover how to set this up later in the guide.

### The configuration

Here's how you tell Vercel to publish redirects at the CDN level:

`import type { VercelConfig } from '@vercel/config/v1' export const config: VercelConfig = { framework: 'nextjs', outputDirectory: '.next', bulkRedirectsPath: './generated-redirects.json', // This is the magic }`

### Real-world example: E-commerce seasonal URLs

In this example, we use [Contentful](https://www.contentful.com/) as the CMS.

**Setup (one time):**

- The marketing team creates a "redirect" content type in Contentful
  
- The developer adds a webhook to trigger Vercel deployments on redirect changes
  

**Every season:**

- The marketing team logs into Contentful
  
- They create an entry: `/catalog/fall` → `/catalog/fall-2025`, with status code 302
  
- They save the entry
  
- The webhook triggers a Vercel deployment
  
- `vercel.ts` runs and fetches 100 redirects from Contentful
  
- Vercel publishes redirects to the CDN across all [regions](https://vercel.com/docs/regions)
  

**Customer experience:**

- A customer clicks a link in an email to `/catalog/fall`
  
- The request hits the nearest Vercel CDN region
  
- The CDN checks bulk redirects and finds a match
  
- The CDN responds with 302 redirect in 5ms
  
- Your Next.js app never executes
  
- Your analytics show 0ms "server response time"
  

## Hands-on: Set up the example

### Prerequisites

- Node.js 20+ and `pnpm` installed
  
- A Contentful account (free tier is sufficient)
  
- Basic understanding of Next.js
  

### Step 1: Clone and install

`pnpm create next-app --example https://github.com/vercel/examples/tree/main/cdn/cms-bulk-redirects cd cms-bulk-redirects pnpm install`

You can also start from [the template](https://vercel.com/templates/cdn/bulk-redirects-via-a-cms).

The demo includes fallback redirects, so you can explore it locally without CMS credentials

### Step 2: Explore without CMS

Start the development server:

`pnpm dev`

Visit `http://localhost:3000` and try these paths:

- `/catalog/fall` - redirects to `/catalog/fall-2025`
  
- `/catalog/latest` - redirects to `/catalog/spring-2026`
  
- `/products/daybreak-pack` - redirects to `/catalog/limited-edition`
  

**Important:** In development mode, Next.js handles these redirects through its dev server, not the CDN. This is just for local testing. In production on Vercel, these same rules run at the CDN level.

Open `generated-redirects.json` to see the fallback rules:

`[{ "source": "/catalog/fall", "destination": "/catalog/fall-2025", "statusCode": 302 }, { "source": "/catalog/latest", "destination": "/catalog/spring-2026", "permanent": true } ]` These are static redirects for development. In production, they come from your CMS. ### Step 3: Set up Contentful To see build-time execution in action, set up a real CMS. You can use the free tier of Contentful. ### Create a redirect content type in Contentful 1. Log into Contentful     2. Go to Content model     3. Add content type named "redirect"     4. Add these fields:     | Field Name    | Type       | Required | Notes                   | | ------------- | ---------- | -------- | ----------------------- | | source        | Short text | Yes      | e.g. `/sale`            | | destination   | Short text | Yes      | e.g. `/catalog/archive` | | statusCode    | Number     | No       | Default: 302            | | permanent     | Boolean    | No       | If true, uses 301       | | caseSensitive | Boolean    | No       | Default: false          | | preserveQuery | Boolean    | No       | Pass query params       | ### Add a sample entry - source: `/sale`    - destination: `/catalog/archive`    - statusCode: `302`    Save and publish the entry. ### Step 4: Configure environment variables Create a `.env.local` file: `CONTENTFUL_SPACE_ID=your_space_id CONTENTFUL_ACCESS_TOKEN=your_cda_token` Find these values in Contentful: - **Space ID:** Settings → General settings    - **Access Token:** Settings → API keys → Content Delivery API    ### Step 5: Build and test Run a production build to see `vercel.ts` fetch redirects from your CMS: `pnpm build` Watch the console output: `> next build fetching contentful redirects ✓ Bulk redirects ready (34 rules) -> /generated-redirects.json` The CMS fetch happened at build time. Open `generated-redirects.json` to see your CMS data as a static file. Now test the redirects: `pnpm dev` Visit `http://localhost:3000/sale` to test your redirect. ### Step 6: Deploy to Vercel Deploy to see CDN-level redirects in action: `vercel --prod` Or connect your GitHub repository in the Vercel dashboard and add the environment variables in project settings. After deployment, test a redirect URL. Open your browser's network tab and visit your deployed URL with a redirect path like `/sale`. This is build-time execution in action. The file runs once during deployment, not on every request. ## Using with your own CMS The pattern works with any CMS. You only need to change the fetch function. ### For Sanity `import { createClient } from '@sanity/client' async function fetchSanityRedirects(): Promise<VercelRedirect[]> { const client = createClient({ projectId: process.env.SANITY_PROJECT_ID, dataset: process.env.SANITY_DATASET, apiVersion: '2024-01-01', useCdn: false, }) const query = '*[_type == "redirect"]{ source, destination, statusCode, permanent }' const redirects = await client.fetch(query) return redirects.map((redirect) => ({ source: normalizePath(redirect.source), destination: normalizePath(redirect.destination), statusCode: redirect.statusCode, permanent: redirect.permanent, })) }` ### For Strapi ``async function fetchStrapiRedirects(): Promise<VercelRedirect[]> { const response = await fetch(`${process.env.STRAPI_URL}/api/redirects`, { headers: { Authorization: `Bearer ${process.env.STRAPI_TOKEN}`, }, }) const { data } = await response.json() return data.map((item) => ({ source: normalizePath(item.attributes.source), destination: normalizePath(item.attributes.destination), statusCode: item.attributes.statusCode, permanent: item.attributes.permanent, })) }`` ## Best practices ### Leveraging build-time execution Set up CMS webhooks to trigger Vercel deployments when redirects change. In Contentful: 1. Settings → Webhooks → Add webhook     2. Trigger on: Entry publish/unpublish for "redirect" content type     3. URL: Your Vercel deploy hook (Project Settings → Git → Deploy Hooks)     **Add redirect validation** in `vercel.ts` to catch errors at build time: ``function validateRedirects(redirects: VercelRedirect[]): void { const sources = new Set<string>() for (const redirect of redirects) { // Check for duplicates if (sources.has(redirect.source)) { throw new Error(`Duplicate source path: ${redirect.source}`) } sources.add(redirect.source) // Check for circular redirects const destinations = redirects.map(r => r.source) if (destinations.includes(redirect.destination)) { console.warn(`⚠️ Potential circular redirect: ${redirect.source} → ${redirect.destination}`) } } }`` ## Other ways to manage redirects The `vercel.ts` approach is ideal for CMS-driven workflows where redirects are fetched at build time and it is sufficient if redirects update on the next deployment. However, you can also manage redirects via the SDK or CLI without waiting for a redeployment. These create project-level redirects that apply immediately across all deployments. ### API/SDK For programmatic management, use the Vercel SDK. For example, you can set up an API route that your CMS calls via webhook whenever redirects change — updating CDN redirects without a redeployment: `import { Vercel } from '@vercel/sdk' const vercel = new Vercel({ token: process.env.VERCEL_TOKEN }) export async function POST(req: Request) { const { redirects } = await req.json() await vercel.bulkRedirects.stageRedirects({ projectId: process.env.VERCEL_PROJECT_ID!, redirects: redirects.map((r) => ({ source: r.source, destination: r.destination, statusCode: r.statusCode ?? 302, })), }) await vercel.bulkRedirects.updateVersion({ projectId: process.env.VERCEL_PROJECT_ID!, action: 'promote', }) return Response.json({ updated: redirects.length }) }` See the [REST API documentation](https://vercel.com/docs/rest-api) for the full documentation for how to use redirects with the Vercel SDK.

### CLI

The `vercel redirects` command also lets you manage bulk redirects directly from the terminal without waiting for a new deployment. See the full [CLI redirects documentation](https://vercel.com/docs/cli/redirects) for all available options.

## Conclusion

### What you accomplished

- Set up a working example that demonstrates redirects set up at build time and running at the CDN level
  
- Explored how `vercel.ts` fetches from your CMS at build time and configures CDN redirects
  

### Next steps

- Deploy your own version with your preferred CMS
  
- Read the [Vercel bulk redirects documentation](https://vercel.com/docs/redirects/bulk-redirects) for advanced configuration
  
- Explore the `@vercel/config` [API reference](https://vercel.com/docs/project-configuration/vercel-ts) for other build-time options

---

[View full KB sitemap](/kb/sitemap.md)
