How to link documentation pages effectively
Internal and anchor links that survive a base path, redirects for reorganizations, and mechanical checks.
Links are what turn a pile of pages into documentation. They let a reader jump to the concept they are missing, follow a process across pages, and find the next step when they finish one. Bad links do the opposite: vague link text, missing cross-references and dead URLs make docs harder to use and weaker in search results.
This guide covers the kinds of links you write in a Reed project, the one rule that catches everyone, and how to keep links intact as the site changes.
Internal links
Section titled “Internal links”Link to other pages with root-relative paths built from the page id:
- [Quickstart](/docs/quickstart)- [Navigation](/docs/authoring/navigation)- [Build output](/docs/ship/output)The path after the base is the page’s id — the same string you list in navigation. The
id comes from the file path, with segments slugified and a nested index collapsed to its
directory. Navigation has the exact rules.
Write the base prefix yourself
Section titled “Write the base prefix yourself”Reed does not rewrite links you write in prose, and it does not rewrite href
attributes on components either. If your docs.json sets base, every internal link
must include it by hand.
On a site with base: "/docs", /docs/quickstart is correct. /quickstart compiles
without a single warning and 404s in production. The same applies to a <Card> or any
other component with an href:
<Card title="Quickstart" href="/docs/quickstart"> From nothing to a deployable site in five minutes.</Card>This is the single easiest mistake to make in a Reed project. If your site has no base,
links start at / with the page id and there is nothing to forget.
Links inside docs.json follow the opposite rule. Footer column links and redirects
are written root-relative without the base, and Reed adds it. Writing
/docs/... there produces /docs/docs/.... site and base lays
both rules out side by side.
Avoid relative paths
Section titled “Avoid relative paths”Reed does not resolve ./sibling or ../other links against the source file. They reach
the browser as written, and the browser resolves them against the URL of the page it is
showing — which depends on how your host serves trailing slashes, not on where the MDX file
sits. Root-relative links behave the same from every page and survive moving the linking
page. Use them.
Anchor links
Section titled “Anchor links”An anchor link points at a section, not just a page. Every heading gets an id derived from its text, so you can link to it without doing anything.
Same page
Section titled “Same page”[Jump to best practices](#best-practices)Another page
Section titled “Another page”Append the heading id to the page path: /docs/site/redirects#real-redirects opens the
redirects page at its “Real redirects” section.
How heading ids are formed
Section titled “How heading ids are formed”The id is the heading text lowercased, with spaces turned into hyphens and most punctuation dropped:
| Heading | Anchor |
|---|---|
## Getting Started |
#getting-started |
### API Authentication |
#api-authentication |
#### Step 1: Install |
#step-1-install |
The consequence: edit a heading and every link to its old anchor stops working. The page still loads, so nothing looks broken — the reader just lands at the top instead of the section. Reed documents no syntax for pinning a heading to a custom id, so treat the wording of headings you link to often as part of their contract, and grep for the old anchor before you change it.
Accordions
Section titled “Accordions”An Accordion takes an id prop that gives it a stable anchor, independent of its
title:
<Accordion title="Installation steps" id="install"> Run the installer, then restart your shell.</Accordion>A link to #install keeps working when you reword the title. See
Layout and surfaces for the component’s props.
External links
Section titled “External links”Absolute http(s) URLs pass through unchanged everywhere. Write link text that says
where the reader is going:
See the [OpenAPI specification](https://spec.openapis.org/oas/latest.html) for the full schema.Best practices
Section titled “Best practices”Write descriptive link text
Section titled “Write descriptive link text”Link text should tell readers where they are going before they click. “Click here” and “read more” tell them nothing, read badly out of context in a screen reader’s link list, and give search engines no signal about the destination.
See [Redirects](/docs/site/redirects) before you move a page.Learn how to [structure your navigation](/docs/guides/navigation).Avoid link text like [click here], [read more] or [this page], whatever it points at.
Link prerequisites up front
Section titled “Link prerequisites up front”When a page depends on earlier steps, link them at the top instead of hoping readers find them:
## Prerequisites
Before you deploy, make sure you have:
- Finished the [Quickstart](/docs/quickstart)- Set `site` and `base` as described in [site and base](/docs/site/addressing)- Reviewed what [the build writes](/docs/ship/output)Build topic clusters
Section titled “Build topic clusters”Link related pages to each other so readers — and search engines — can see how your
content fits together. A short related-pages section at the end of a page does this well,
as does a CardGroup of next steps:
<CardGroup cols={2}> <Card title="Redirects" href="/docs/site/redirects">Keep old URLs alive.</Card> <Card title="site and base" href="/docs/site/addressing">The two link rules.</Card></CardGroup>Check links mechanically
Section titled “Check links mechanically”Reed does not check the links in your prose. reed check validates docs.json and
confirms every page id in navigation resolves, but a broken [text](/docs/...) in a
paragraph compiles cleanly. On a site with a base, a forgotten prefix is exactly that
kind of link.
So check them yourself. This documentation site runs a short script,
scripts/check-links.mjs, in its build script before reed runs. It:
- scans every
.mdxfile for markdown link destinations](/path)and JSX attributeshref="/path", - skips inline code, so a page can show a wrong link as a counter-example,
- still checks fenced code blocks, because readers copy those,
- fails if a root-relative link is missing the
/docsbase or names a page id that has no file.
It fits in about eighty lines. Writing one for your own project is an afternoon’s work,
and it catches the base-prefix mistake before your readers do. If you extend it to anchor
links, strip the #fragment before looking up the page id.
For wider coverage — anchors, external URLs, assets — run a third-party link checker against the built output. Build the site, serve the output directory locally, and crawl it. That checks what readers actually get rather than what the source says.
Update links when you reorganize
Section titled “Update links when you reorganize”When you move or rename a page:
- Move the file and update its entry in
navigation. - Add a redirect from the old path to the new one.
- Search the content directory for the old path.
- Update every internal link that uses it.
- Run
reed checkand your link checker.
Redirect moved content
Section titled “Redirect moved content”A bookmark, a search result or a link from a blog post does not know you moved the page. A redirect keeps it working:
{ "redirects": [ { "source": "/old-guide", "destination": "/guides/getting-started" } ]}Write source and destination without the base — Reed adds it. Reed writes each
redirect into a _redirects file, which Cloudflare Pages and Workers serve as a real 301
or 302, and into an HTML page at the old path that meta-refreshes to the destination and
points its canonical link there. Hosts that do not apply _redirects, and Netlify unless
you add a forced rule, serve that page instead. A source must not be the path of a page
that is still in navigation: reed check reports that as redirect-source-conflict. There are no
wildcards, so moving a whole section means one entry per page.
Redirects covers the details, including when the permanent flag
matters.
Frequently asked questions
Section titled “Frequently asked questions”Should internal links be root-relative or absolute URLs?
Root-relative, starting with your base. They work from any page, survive a change of domain, and are what your link checker can verify against page ids. Absolute URLs to your own site add a dependency on the origin for no benefit. Relative paths are the worst option: Reed passes them through untouched, so they resolve against the served URL rather than the source file.
How do I keep anchor links stable when I edit headings?
Treat the wording of frequently linked headings as fixed, and search for the old anchor whenever you change one. For content inside an accordion, set the accordion’s id prop, which gives it an anchor that does not depend on its title.
What happens to bookmarked links when I reorganize?
Without a redirect, they 404. Add a redirect in docs.json for every page whose URL moves. Redirects are one line each and they protect every link you do not control — blog posts, forum answers, internal wikis, search results.
How many internal links should a page have?
As many as genuinely help the reader at that moment, and no more. Too few leave readers without context or a next step. Too many turn the page into a detour. A good default is to link the first mention of a concept and not repeat the same link further down the page.