---
title: Fix links that work in reed dev but 404 in production
description: Reed never rewrites links in prose or component hrefs. On a site with a base, you write the prefix yourself.
---

## Symptom

A link inside a page points at the wrong URL on the deployed site. The page exists, the
build passed, `reed check` passed — and clicking the link lands on a 404. Typically:

- the site sets `base: "/docs"` in `docs.json`;
- a page contains `[Quickstart](/quickstart)` or `<Card href="/quickstart">`;
- the deployed link goes to `https://example.com/quickstart`, which does not exist,
  because the page lives at `https://example.com/docs/quickstart`.

There is no warning at any stage. Nothing in `reed check` or `reed build` inspects the
targets of links you write in MDX.

## Cause

**Reed does not rewrite links you write in prose, and it does not rewrite `href`
attributes on components.** A link target reaches the HTML exactly as you typed it.

`base` prefixes the links Reed builds itself — the sidebar, asset references, the
redirect pages — but a link inside your MDX is yours. On a site with `base: "/docs"`, every
internal link has to be written `/docs/<pageId>`. Writing `/quickstart` compiles clean and
404s.

Links inside `docs.json` are split. Footer column links (and the footer status link),
redirects and `head.link` entries are root-relative *without* the base, and Reed adds it.
Writing `/docs/quickstart` there produces `/docs/docs/quickstart`. **Navbar links are the
exception:** Reed passes `navbar-links` URLs through verbatim, so a root-relative navbar
link has to include the base, exactly like a link in your MDX.

| Where you write the link | What you write on a `base: "/docs"` site |
|---|---|
| Page prose: `[text](...)` | `/docs/quickstart` |
| Component attribute: `href="..."` | `/docs/quickstart` |
| `docs.json`: `navbar-links` | `/docs/quickstart` |
| `docs.json`: footer columns, redirects, `head.link` | `/quickstart` |

If you moved a site from no `base` to `base: "/docs"`, every root-relative link in your
content broke at once, silently.

## Fix

### Correct the links

Add the base prefix to every internal link in your MDX. The path after the prefix is the
page id — the same string you list in `navigation`:

```mdx
See the [Quickstart](/docs/quickstart) and [site and base](/docs/site/addressing).
```

A quick way to find offenders on a `/docs` site:

```sh
grep -rnE '\]\(/|href=./' content --include='*.mdx' | grep -v '/docs/'
```

In `docs.json`, footer, redirect and `head.link` URLs that include the base should lose it;
navbar links that lack it should gain it.

### Check them mechanically

Reading links by eye does not scale, and the next one will slip through. Reed's own
documentation fails its build on this mistake with a short script,
`scripts/check-links.mjs`, run before `reed build`. It:

- walks every `.mdx` file under `content/` and builds the set of page ids from the file
  paths;
- matches both link forms MDX has: markdown destinations `](/path)` and JSX attributes
  `href="/path"` (checking only the first would leave every `<Card>` link unvalidated);
- reports any root-relative link that does not start with `/docs/`, and any `/docs/...`
  link whose page id does not exist;
- scans fenced code blocks too, because readers copy from them — only inline code spans
  are skipped, so a page can still show a wrong link as a counter-example;
- exits non-zero on the first problem set, so it can gate a build.

It runs as the first step of `scripts/build.sh`:

```sh
echo "==> check content links"
node "$REPO_ROOT/scripts/check-links.mjs"
```

Copy the idea into your own project: set the base constant to yours, and run it in CI
before `reed build`. A failing link check costs seconds; a 404 in production costs readers.

<Note>
  Absolute `http(s)` URLs are passed through verbatim everywhere and need no prefix. Avoid
  relative links such as `./sibling` — Reed does not resolve them against the source file,
  so they resolve against whatever URL the browser is on.
</Note>

## Verify

Run the check, then build and look at the emitted HTML for the link you fixed:

```sh
node scripts/check-links.mjs
reed build --out dist
grep -o 'href=./docs/quickstart.' dist/index.html | head -3
```

Then confirm on the deployed site that the target answers 200, not 404:

```sh
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/docs/quickstart
```

`reed dev` is not proof either way — it serves on its own origin and port, so test the
links against a real build.

## Related

- [Links](/docs/authoring/pages) — the authoring rule in the page reference
- [site and base](/docs/site/addressing) — both link rules side by side
- [How to link documentation pages effectively](/docs/guides/linking)
- [Redirects](/docs/site/redirects) — when a page moves rather than a link breaking
