Redirects on a Static Site: Status Codes, Order, and the 2,100 Limit
A static site cannot inspect a request and decide where to send it. What it can do is ship a text file of rules that the platform reads before serving anything, and that file is the only redirect mechanism you have.
Cloudflare Pages’ _redirects file is well documented, which makes it a good model for learning the concepts — including a default status code that differs from its main competitor’s, and a limit that is two limits wearing one number.
The syntax
One rule per line, three fields:
/old-page/ /new-page/ 301
- Field one: the source path
- Field two: the destination — a path, or a full URL for an external redirect
- Field three: an optional status code, defaulting to 302
The default is worth flagging because Netlify’s file uses the same shape and defaults to 301 instead. The same rule written for the two platforms produces different semantics: a 302 tells search engines the move is temporary and keeps the old URL indexed, while a 301 tells them to transfer ranking signals. Getting this backwards is the single most common redirect mistake, and it is invisible unless you check the status code.
Lines have a documented maximum length of 1,000 characters, and # starts a comment.
The limit that is two limits
Cloudflare documents “2,000 static redirects and 100 dynamic redirects, for a combined total of 2,100 redirects.”
So it is not “2,100 redirects.” It is 2,000 of one kind and 100 of another, summing to 2,100. If your site needs 500 rules with placeholders or wildcards, you are over the dynamic budget regardless of how few static rules you have.
The distinction matters because the dynamic rules are the expressive ones. The static rules are the bulk.
Compare the other platforms: Vercel allows up to 2,048 redirects in its configuration file, with a 4,096-character limit per source and destination. Netlify documents a recommendation to use wildcards and placeholders once you get into the thousands, and warns that an oversized serialized output “might fail” the deploy — a failure that happens at build time rather than as a runtime error, which is at least honest.
For a content site, 2,000 rules is a lot. For a site migrating from a legacy structure with tens of thousands of URLs, it is not, and the correct answer is pattern rules rather than enumerated ones.
Order decides everything
This is the documented behaviour that surprises people, and it is unusually explicit:
“The order of your redirects matter. If there are multiple redirects for the same source path, the top-most redirect is applied. Static redirects should appear before dynamic redirects. Redirects are always followed, regardless of whether or not an asset matches.”
Three clauses, all load-bearing:
Top-most wins. It is first-match, not most-specific-match. This differs from how many people reason about routing, and it means a broad rule placed at the top of the file shadows everything below it.
Static before dynamic. The documentation states this directly, which is a hint about how the platform evaluates them — the two categories are processed separately, and the ordering instruction is there because getting it wrong produces wrong results.
Redirects beat assets. A redirect is followed even when a file exists at that path. So a rule that matches a page you still have will redirect it, and the page becomes unreachable. If you are adding rules for URLs you also serve, that is the bug you will spend an hour on.
Netlify’s documented precedence is similar in spirit — rules are evaluated top to bottom and “the first matching rule” wins — with the additional note that _redirects is always processed before configuration in netlify.toml.
There is also a documented interaction on Cloudflare that decides ordering between two different files: “Redirects execute before headers, so in the case of a request matching rules in both files, the redirect will win out.” If a path is both redirected and has custom headers, the redirect happens and the headers never apply.
The rewrite, which is not a redirect
The most-copied rule in static hosting is this one:
/* /index.html 200
This is not a redirect. A 200 status in the destination position means the platform serves the destination content at the original URL — a rewrite, or a proxy. The visitor’s address bar does not change, and no redirect is issued.
Three documented constraints on it:
- Relative URLs only. An external destination with a 200 is not supported.
- Only the first such rule applies. So you cannot have two rewrite rules and expect both to function; the file has one rewrite, effectively.
- The documentation warns about duplicate content and suggests emitting a canonical link header for the rewritten path.
On Netlify the same syntax exists with the same meaning, and Netlify documents a ! prefix to force a rule that would otherwise be shadowed.
This pattern is for single-page applications, where every path must be served by one HTML file. A content site should not use it. If you rewrite everything to index.html, every URL on your site serves the same page to a crawler, which is a worse outcome than a 404.
What _redirects cannot do
The documented limitations, which are worth reading before you design around it:
| Feature | Supported? |
|---|---|
| 301, 302, 303, 307, 308 | Yes |
Splats (*, greedy) | Yes — one per URL |
Placeholders (:name) | Yes — each referenced once |
| Query string matching | No |
| Domain-level redirects | No |
| Country or cookie conditions | No |
| Rewrites with codes other than 200 | No |
The query string limitation is the one that shapes migrations. If you are consolidating two sites and need to redirect URLs that differ by query parameter, _redirects will not see the parameter. The documented workaround is a splat rule that captures the path and drops the query, which handles most cases but not conditional logic.
Platform differences worth knowing before you migrate
| Cloudflare Pages | Netlify | Vercel | |
|---|---|---|---|
| Default status | 302 | 301 | — |
| Rule limit | 2,000 static + 100 dynamic | Documented via wildcards at scale | 2,048 |
| Precedence | Top-most wins, static first | First matching rule | Config rules |
| Trailing slash control | — | Documented as not possible via rules | — |
| Normalisation | — | CDN normalises before rules | Runs before your rules; // → 308, case-sensitive |
Two of those rows deserve highlighting. Netlify documents that its CDN normalises trailing slashes before your rules run, so paths match with or without one — and that you cannot use a redirect rule to add or remove a trailing slash. If your SEO plan involves canonicalising trailing slashes, that has to happen somewhere other than the redirect file.
Vercel documents that normalisation likewise runs before your rules, and that URLs are case-sensitive. A redirect intended to fix uppercase variants may never fire, because the normalisation step already handled or rejected the request.
A migration routine
- Write the file with explicit status codes. Never rely on the default — the defaults differ between platforms, and a 302 where you wanted a 301 can cost ranking signals.
- Put specific rules above broad ones. First match wins.
- Put static rules before dynamic ones, as documented.
- Check for collisions with real content. A redirect beats an asset, so a rule can silently hide a live page.
- Verify the status codes on the deployed site, not just the file:
curl -sI https://example.com/old-page/ | head -3
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://example.com/old-page/
- Update internal links rather than relying on redirects. A redirect is a permanent tax on every visit; a corrected link is free.
When not to use redirects at all
If you are restructuring a site whose URLs never appeared in public, delete the old paths and let them 404. Redirects exist to preserve signal and prevent broken bookmarks — neither applies to a URL that was never indexed or linked.
And if you are tempted to redirect everything to the homepage after a restructure, check what that communicates: a crawler sees a site where every old URL resolves to the same page, which is a soft-404 pattern. A specific 404 is more honest and often ranks better than a blanket redirect.
Written by TestedHost. Every recommendation on this site comes from running the setup described, on a live deployment — not from a vendor spec sheet. Spotted something out of date? Tell us.