Configuring Redirects on Cloudflare Pages

Every static site accumulates redirects: renamed pages, a migration from WordPress, a new URL scheme for docs versions, http to https and apex to www. On Cloudflare Pages, most of them belong in a plain-text _redirects file deployed with the site. It is simple, versioned with your content and applied at the edge before any file is served. It also has limits that bite during large migrations, and some rules are better handled elsewhere.

This guide covers the _redirects syntax, the limits and ordering rules, testing redirects before deploy, and when to move rules to Bulk Redirects, Redirect Rules or a Worker. It is part of Cloudflare Pages Edge Caching Setup.

Prerequisites

  • A site deployed to Cloudflare Pages, or to Workers with static assets — the _redirects format is the same.
  • A list of old and new URLs, for example from a migration spreadsheet.
  • wrangler installed locally for testing: npm install -D wrangler.

The _redirects File

Place _redirects in the build output directory — for most generators, put it in the static or public folder so it is copied unchanged. Each line is a source, a destination and an optional status code:

# Exact paths
/old-page/                 /new-page/                 301
/docs/v1/install/          /docs/install/             301

# Splat: everything under a path
/blog/*                    /articles/:splat           301

# Placeholders
/docs/:version/api/:name/  /reference/:name/?v=:version  301

# External destination
/community/                https://forum.example.com/ 302

# Rewrite (200): serve another path without changing the URL
/app/*                     /app/index.html            200

The status defaults to 302 if omitted. Use 301 or 308 for permanent moves so search engines transfer signals and browsers cache the redirect; use 302 or 307 for temporary ones. Rules are evaluated top to bottom and the first match wins, so put specific rules above general ones.

Where each redirect mechanism runs on Cloudflare A request first passes account-level Bulk Redirects and zone-level Redirect Rules, then reaches the Pages project, where the _redirects file is checked before static assets and Pages Functions. The earlier the mechanism, the less work each request does. Order of evaluation for a request request old.example.com/x Bulk Redirects account, millions Redirect Rules zone, expressions Pages project _redirects (2,100 rules) static assets Functions / Worker code a match at any stage returns the redirect immediately; later stages never run
Rules in _redirects are checked after zone-level rules but before any file or function.

Limits and What They Mean

A _redirects file supports up to 2,000 static and 100 dynamic rules. Static rules match one exact path; dynamic rules contain a splat or placeholder. Lines past the limits are silently ignored, which is the most common source of "some redirects do not work" after a migration. Each line may be up to 1,000 characters.

For migrations, generate the file rather than writing it by hand. A build step that reads the old-to-new mapping from a CSV and writes _redirects into the output directory keeps the source of truth readable and lets you check the counts:

// scripts/build-redirects.mjs
import { readFileSync, writeFileSync } from 'node:fs';
const rows = readFileSync('redirects.csv', 'utf8').trim().split('\n').slice(1);
const lines = rows.map((r) => r.split(',')).map(([from, to]) => `${from} ${to} 301`);
if (lines.length > 2000) throw new Error(`${lines.length} static redirects exceeds the 2,000 limit`);
writeFileSync('dist/_redirects', lines.join('\n') + '\n');

Failing the build is better than silently dropping rules. If you are over the limit, look for patterns first: 3,000 exact-path redirects from /blog/2019/05/slug/ to /articles/slug/ often collapse into one dynamic rule.

Trailing Slashes and Case

Pages serves /page/index.html at /page/ and redirects /page to /page/ by default, so source paths in _redirects should match the form readers actually request. For old URLs that may appear with and without the slash, add both, or normalise with a single dynamic rule. Paths are case-sensitive; if the old site was case-insensitive, lowercase URLs in the migration list and add a Worker or Redirect Rule for mixed-case variants. Trailing slash conventions across generators are covered in Clean URLs and Trailing Slashes on S3.

Testing Before Deploy

Run the site locally with Wrangler, which applies _redirects the same way production does:

npx wrangler pages dev dist

Then check each redirect with curl, looking for one hop to the right destination with the right status:

while IFS=, read -r from to; do
  loc=$(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' "http://localhost:8788$from")
  echo "$from -> $loc"
done < <(tail -n +2 redirects.csv)

Run the same loop against the preview deployment URL that Pages creates for each branch, so the check runs on real infrastructure before the change reaches production. Watch for chains — a redirect to a URL that itself redirects. Each hop costs a round trip, which shows up directly in LCP for readers arriving from old links. Rewrite the source so it points to the final destination.

A redirect chain compared with a single hop A chained old URL redirects to http-less, then adds a trailing slash, then goes to the new path, costing three round trips of about 120 milliseconds each on mobile. The flattened rule goes directly to the final URL in one hop. Flatten chains to one hop Chained: 3 hops, ≈ 360 ms /blog/post /blog/post/ /articles/post /articles/post/ Flattened: 1 hop, ≈ 120 ms /blog/post /articles/post/ round-trip estimate for a mobile connection with a warm TLS session
Every hop is a full round trip before the reader's page even starts loading.

When to Use Something Else

  • Bulk Redirects for more than 2,000 exact-path rules, redirects shared across projects or domains, or redirects changed by non-developers without a deploy. They are configured in the dashboard or API as lists and run before the request reaches Pages.
  • Redirect Rules (single redirects) for pattern-based rules across a whole zone, such as apex to www or http to https, using Cloudflare's expression language.
  • A Worker or Pages Function for logic that depends on headers, cookies or geography — for example, redirecting / to /de/ for German-speaking readers. Keep this rare: code on every request costs more than a list lookup.
Choosing a redirect mechanism by need A table of four mechanisms. _redirects: up to 2,100 rules, versioned with the site, changes need a deploy. Bulk Redirects: millions of rules, account-wide, no deploy. Redirect Rules: pattern expressions per zone. Worker: any logic, runs code on every request. Pick the lightest mechanism that fits mechanism capacity change without deploy best for _redirects 2,000 + 100 no per-site moves Bulk Redirects millions yes big migrations Redirect Rules expressions yes host, scheme Worker any logic no geo, cookies list lookups are cheaper than code; reach for a Worker only when a list cannot express the rule
Most sites need only _redirects plus one or two zone-level Redirect Rules.

Redirects and Caching

Permanent redirects are cached by browsers, sometimes for a long time and without a way for you to clear them. That is what makes 301s fast for returning readers, and also what makes a wrong 301 painful: a reader who followed it once may keep being sent to the wrong place until their cache expires. During a migration, deploy new rules as 302 for a day or two, check analytics and Search Console for surprises, then switch to 301.

At the edge, Cloudflare serves _redirects responses directly without touching the origin, so redirects add no load. They also do not appear in the cache analytics as hits or misses, which can make a redirect-heavy migration look like a traffic drop in dashboards that only count cached responses. Check the redirect counts in the Pages analytics or with a Logpush dataset if numbers look off.

Measured Impact

A documentation site migrating from a self-hosted wiki to Astro on Cloudflare Pages had 6,400 old URLs. The first deploy put all of them in _redirects and 4,400 silently did nothing. Collapsing date-based blog paths into four dynamic rules brought the exact-path list to 1,850, which fit; the remaining legacy wiki paths moved to a Bulk Redirect list. Search Console's "Not found (404)" count for the domain dropped from 5,100 to under 100 within three weeks, and organic landings on migrated pages recovered to pre-migration levels within two months.

Pitfalls & Rollback

  • Rules after the limit. Silently ignored; count them in the build.
  • Order. A broad splat above specific rules captures everything; put specific rules first.
  • Query strings. Source paths do not match query strings; the query is passed through to the destination unless the destination defines its own.
  • Redirect loops. A rule whose destination matches its own source loops until the browser gives up; test with curl -L --max-redirs 5.
  • Rollback: redeploy the previous build; _redirects is part of the deployment, so an instant rollback restores the old rules too.

Conclusion

For most static sites on Cloudflare Pages, a generated _redirects file in the build output handles every redirect with no extra services: exact paths, splats and placeholders, applied at the edge before any file. Generate it from a mapping, fail the build if it exceeds the limits, test for single hops locally with Wrangler, and move anything larger or cross-project to Bulk Redirects.

FAQ

How many redirects can a Cloudflare Pages _redirects file hold?

Up to 2,000 static redirects and 100 dynamic redirects, for a combined total of 2,100 rules, with each line limited to 1,000 characters. Rules beyond the limit are ignored, so large migrations should use Bulk Redirects instead.

What is the difference between static and dynamic redirects on Pages?

A static redirect matches one exact path. A dynamic redirect uses a splat (asterisk) or a named placeholder such as to match many paths. Dynamic rules are more expensive to evaluate and have a much lower limit.

Does a _redirects rule apply before or after static files?

Redirects in _redirects are applied before static assets are served, so a rule can redirect a path even when a file exists there. Rewrites with a 200 status also work for proxying to other paths on the same project.

When should I use Bulk Redirects instead of _redirects?

When you have more than about 2,000 exact-path redirects, need redirects shared across several projects or domains, or want to change redirects without redeploying. Bulk Redirects are configured at the account level and support millions of entries.