Redirects
What a redirect entry compiles to, the _redirects file, and which hosts serve a real 301 or 302.
{ "redirects": [ { "source": "/old-guide", "destination": "/guides/getting-started" }, { "source": "/legacy", "destination": "https://example.com/elsewhere", "permanent": false } ]}sourcestringrequiredSite-root-relative path, without the base prefix — Reed adds it.
destinationstringrequiredSite-root-relative path, or an absolute http(s) URL.
permanentbooleandefault: truetrue writes a 301, false a 302. The status is real on hosts that apply the
_redirects file; see below.
What a redirect compiles to
Section titled “What a redirect compiles to”reed build writes two things for each entry.
A _redirects file at the root of the output, next to _headers. It has one line per
entry: the source, the destination and the status, with no trailing !. Both columns
carry the base prefix when they are root-relative; an absolute destination is written
as-is. With base: "/docs", the example above becomes:
/docs/old-guide /docs/guides/getting-started 301/docs/legacy https://example.com/elsewhere 302The file is written whether or not site is set. A project with no redirects entries
gets no _redirects file. The redirect Reed adds for the site root, when no page lives at
/, is not written to it.
A meta-refresh page at the source path, as before. It carries a
<meta http-equiv="refresh"> to the destination, a noindex robots tag, a canonical link
to the destination and a visible link for anyone whose browser does not follow the
refresh. It is the fallback for hosts that do not read _redirects.
What a reader gets depends on the host:
| Host | Result |
|---|---|
| Cloudflare Pages, Cloudflare Workers static assets | A real 301 or 302. Cloudflare applies _redirects rules before serving a static file at the same path. |
| Netlify | The meta-refresh page, served with a 200. Netlify lets a static file at the same path win over a _redirects rule. Add a forced rule in netlify.toml for a real status. |
A host that does not read _redirects |
The meta-refresh page, served with a 200. |
On Cloudflare, permanent now changes what readers and crawlers receive. Think before
accepting the default true: a 301 is cached aggressively by browsers and intermediaries,
and undoing one in the field is genuinely hard. Use false while a move is still
provisional. On other hosts, true and false produce the same meta-refresh page. If
you need a real status there, add the rule at the host or CDN.
Duplicate sources are an error
Section titled “Duplicate sources are an error”Two entries whose source normalizes to the same path is a schema error. Previously this
was a silent last-one-wins, which is the kind of behaviour that makes a redirect table
impossible to reason about as it grows. Now the config simply refuses to build.
A source must not shadow a page or a generated file
Section titled “A source must not shadow a page or a generated file”A source that equals the route of a page in navigation is an error. So is a source
that equals the path of a file the build writes: /llms.txt, /_headers, /_redirects
and every page’s .md mirror, plus /robots.txt, /sitemap-index.xml and
/sitemap-0.xml when site is set. reed check and reed build both report it as
redirect-source-conflict, naming the source and what it collides with, and the build
writes nothing:
✗ [redirect-source-conflict] docs.json redirects entry source "/guides/intro" collides with the route of navigation page "guides/intro"; a redirect source must not shadow an existing pageA trailing slash makes no difference: /guides/intro/ collides the same way. To redirect
away from a page, remove it from navigation and the content directory first.
Both prefix rules in one place
Section titled “Both prefix rules in one place”source and destination follow the docs.json rule: root-relative values are
base-prefixed by Reed. So on a site with base: "/docs", a source of /old-guide
matches the served URL /docs/old-guide.
This is the opposite of what you write in page prose, where you supply the prefix
yourself. If you find yourself writing /docs/... in a redirect, you have almost
certainly produced /docs/docs/....
What redirects cannot do
Section titled “What redirects cannot do”There is no pattern matching, no wildcard, and no capture-group substitution — each entry is one exact path to one destination. Moving a whole subtree means one entry per page.
That is a real limitation. When the list gets long enough to be painful, the alternative is a rule at your host or CDN layer, which is outside Reed and stays outside it.