Redirects and rewrites
Add a file named _redirects to the root of your asset directory to send one path
to another, serve a file’s contents at a different URL, or set custom error pages.
The syntax follows Netlify’s _redirects,
with one rule per line:
/old-path /new-path 301Each line is three whitespace-separated fields: from, to, and status. Blank
lines and lines starting with # are ignored.
The three fields
Section titled “The three fields”from is the path to match, always absolute (starts with /):
/about: an exact path./blog/*: a subtree, everything under/blog/. The*is only allowed as a trailing/*./*: the entire site.
to is the destination:
- For a redirect (3xx), an absolute path (
/new) or a full URL (https://example.com/new). - For a rewrite or error page (200/404/410), an absolute path to one of your files.
status is what the canister does. One of:
| Status | Meaning |
|---|---|
301 | Permanent redirect. |
302 | Temporary redirect. |
307 | Temporary redirect, preserving the request method. |
308 | Permanent redirect, preserving the request method. |
200 | Rewrite: serve the to file’s contents at the from URL, with no visible redirect. |
404 | Serve the to file as a not-found page. |
410 | Serve the to file, signalling the resource is permanently gone. |
A 404 or 410 error page must be a small, single-chunk file (under ~1.9 MB).
Large files are served as 206 range responses
that the gateway reassembles into a 200, which can’t carry a 4xx status, so the sync
plugin rejects a 4xx rule pointing at a multi-chunk asset at deploy time, naming the
offending rule. (A 200 rewrite to a large file is fine.)
Examples
Section titled “Examples”# Permanent redirect to a new internal path/old-blog /blog 301
# Redirect to an external site/discord https://discord.gg/example 302
# Rewrite: serve /content/article.html at the pretty URL, no redirect/article /content/article.html 200
# Subtree redirect: everything under /docs/v1/ moves to /docs/v2//docs/v1/* /docs/v2/ 301
# Single-page-app fallback: serve the shell for any unmatched path/* /index.html 200
# Custom error pages/secret /403.html 404/retired-feature /sunset.html 410Ordering & precedence
Section titled “Ordering & precedence”Rules are evaluated top to bottom and the first match wins, within a fixed overall order:
- Files. An actual file at the requested path always wins over any rule. (This
is why there’s no Netlify-style
!“force” suffix: to override a file, remove or rename it rather than forcing a rule past it.) - Clean-URL rules. The automatic clean-URL redirects are applied next.
- Your
_redirects. Then your rules, in file order. - The 404 fallback. Finally the not-found page.
So a /* rule at the end of your file only catches paths that nothing earlier
claimed, which is exactly what makes the
SPA fallback above safe.
What isn’t supported: dynamic rules
Section titled “What isn’t supported: dynamic rules”There are no :splat or :placeholder captures: you can’t write
/old/:rest /new/:rest 301 to forward a captured path segment. This isn’t a
missing feature we plan to add; it’s a consequence of how the Internet Computer
serves these assets.
Every response the canister can return is certified ahead of time. During sync,
the plugin builds a certification tree over a finite, enumerable set of
path → response mappings, and a verifying gateway checks each response against that
tree before serving it. A :splat rule would construct its destination (and thus
its response) dynamically from whatever path the visitor requested, producing
responses that were never certified. The canister would have no certified response to
serve, and a verifying gateway would reject one anyway. (The same constraint is why
header values can’t use :splat substitution either.)
Static rules (exact paths, /* subtrees, and fixed destinations) cover the common
cases and stay fully certifiable, so those are what _redirects supports.