---
title: Redirects
description: Real redirects, compiled — not a client-side hack.
---

```json
{
  "redirects": [
    { "source": "/old-guide", "destination": "/guides/getting-started" },
    { "source": "/legacy", "destination": "https://example.com/elsewhere", "permanent": false }
  ]
}
```

<ParamField name="source" type="string" required>
  Site-root-relative path, **without** the `base` prefix — Reed adds it.
</ParamField>

<ParamField name="destination" type="string" required>
  Site-root-relative path, or an absolute `http(s)` URL.
</ParamField>

<ParamField name="permanent" type="boolean" default="true">
  `true` emits a 301, `false` a 302.
</ParamField>

## Real redirects

These compile into actual redirects in the built output, with an explicit status code —
not a meta-refresh and not a client-side script. A crawler following one gets the status
you asked for.

`permanent` defaults to `true`. Think before accepting it: 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.

## 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.

## 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

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.
