> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt. # Configure redirects Redirects map old URLs to new ones so inbound links and search rankings survive when pages move, change slugs, or are deleted. They apply to [Markdown URLs](/learn/docs/ai-features/markdown) too: a redirected page's `.md` or `.mdx` twin returns a `308` to the destination's twin. ## Set up redirects Configure redirects to internal paths or external URLs in `docs.yml` or in [separate files](#keep-redirects-in-separate-files) that `docs.yml` references. Use an exact path when a single URL moves, and a [pattern](#pattern-syntax) when a path's descendants move with it: an exact `source` matches that one URL and nothing beneath it, so `/old-folder` leaves `/old-folder/page` alone. If your docs are hosted on a subpath (like `buildwithfern.com/learn`), include the subpath in both the source and destination paths. #### docs.yml ```yml redirects: # Exact path redirects - source: "/old-path" destination: "/new-path" - source: "/old-folder/path" destination: "/new-folder/path" - source: "/old-folder/path" destination: "https://www.example.com/fern" # External destination - source: "/temporary-redirect" destination: "/new-location" permanent: false # Use 307 (temporary) instead of 308 (permanent) # Pattern-based redirects - source: "/old-folder/:slug" # Matches single segments: /old-folder/foo destination: "/new-folder/:slug" - source: "/old-folder/:slug*" # Matches multiple segments: /old-folder/foo/bar/baz destination: "/new-folder/:slug*" - source: "/old-:slug(.*)" # Matches mid-segment: /old-guides/setup destination: "/new-:slug(.*)" ``` ### Pattern syntax \[#pattern-syntax] A parameter is a name prefixed with `:`. Each one captures part of the incoming URL and is replayed in `destination` under the same name. | Pattern | Example `source` | Matches | | -------------- | ------------------- | ------------------------------------------------- | | `:slug` | `/plants/:slug` | `/plants/monstera`, not `/plants/indoor/monstera` | | `:slug*` | `/plants/:slug*` | `/plants` and `/plants/indoor/monstera` | | `:slug+` | `/plants/:slug+` | `/plants/indoor/monstera`, not `/plants` | | `:slug(regex)` | `/plants-:slug(.*)` | `/plants-indoor/monstera` | | `(regex)` | `/plants/(\d+)` | `/plants/42` | An unnamed group has no name to replay, so it appears in `destination` as `:0`. A regular expression is the only way to match mid-segment, because `:slug`, `:slug*`, and `:slug+` all have to follow a `/`. It also narrows a parameter rather than widening one: `/v:version(\d+)/plants/:slug*` matches `/v2/plants/monstera/care` and captures `version` as `2`. Repeat the regular expression in `destination`. A parameter written as `:slug(.*)` in `source` stays `:slug(.*)` in `destination`, because a bare `:slug` can't hold a value containing `/`; omitting it resolves the redirect to the literal text `/new-folder/:slug`. The optional modifier (`?`) is unsupported. Fern strips each `source` at the first `?` to remove search parameters, so `/old-folder/:slug?` is matched as `/old-folder/:slug`. ## Keep redirects in separate files Set `redirects` in `docs.yml` to one or multiple YAML file paths: #### One file **`docs.yml`** ```yaml title="docs.yml" redirects: ./redirects.yml ``` #### Multiple files **`docs.yml`** ```yaml title="docs.yml" redirects: - ./redirects/api.yml - ./redirects/guides.yml ``` Each file must contain a top-level `redirects` list: **`redirects.yml`** ```yaml title="redirects.yml" redirects: - source: "/old-path" destination: "/new-path" - source: "/old-folder/:slug*" destination: "/new-folder/:slug*" ``` File paths are relative to `docs.yml`. Fern evaluates the files in the order listed. Use file paths or inline redirect entries, but not both. ## Evaluation order Redirects are evaluated top-to-bottom and the first match wins, so list specific paths before broader ones: #### docs.yml ```yml redirects: # Specific path first — matches /docs/api/billing/overview, etc. - source: "/docs/api/billing/:slug*" destination: "/docs/reference/billing/:slug*" # Broader catch-all second — matches everything else under /docs/api/ - source: "/docs/api/:slug*" destination: "/docs/reference/:slug*" ``` ## Properties Each entry under `redirects` accepts the following properties. **`source`** `string` — required The relative path you want to redirect from (e.g., `/old-path`). Must be a relative path, not an absolute URL. Must not include search parameters (e.g., `?key=value`). Trailing slashes are normalized before matching, so `/old-path/` and `/old-path` are equivalent. Match the casing of the URL you're redirecting from. --- **`destination`** `string` — required The path you want to route to. Can be an internal path (`/new-path`) or an external URL (`https://example.com`). External URLs must include the full address, including `https`. --- **`permanent`** `boolean` — default: true By default, uses the 308 status code to instructs clients and search engines to cache the redirect forever. Set to `false` only if you need a temporary redirect using the 307 status code, which won't be cached. --- ## When not to add a redirect For optimal site performance, only add redirects when necessary. Fern already handles 404s and version routing, and redirects that duplicate those behaviors work against them. ### 404 handling Don't redirect broken links to your homepage: **`docs.yml`** ```yaml title="docs.yml" redirects: - source: /docs/event-notifications destination: / # Don't do this ``` Instead, [enable automatic homepage redirects in your `docs.yml`](/learn/docs/configuration/site-level-settings#settings-configuration) to send broken links to your homepage rather than showing a 404 page: **`docs.yml`** ```yaml title="docs.yml" settings: hide-404-page: true ``` ### Versioning If you have [versions](/learn/docs/configuration/versions) configured, your default version uses unversioned paths (`/docs/getting-started`), while other versions use versioned paths (`/docs/v2/getting-started`). Fern automatically handles version routing by redirecting broken versioned links to the default version and managing canonical URLs. Avoid redirecting from unversioned to versioned URLs: **`docs.yml`** ```yaml title="docs.yml" redirects: - source: /docs/event-notifications destination: /docs/v2/event-notifications # Don't do this ``` Overriding the default versioning behavior manually can lead to unexpected redirect patterns. If you frequently need to redirect from the default version to another version, change which version is set as default in your versions configuration. ## Catching missing redirects The [`missing-redirects` rule](/learn/docs/configuration/site-level-settings#check-configuration), run by [`fern check`](/learn/cli-api-reference/cli-reference/general-commands#fern-check), compares the navigation tree built from your local YAML against the most recently **published** state of your site and flags previously published URLs that no longer resolve and aren't covered by an entry in `redirects:`. This catches pages you've moved or removed before they start returning 404s for existing inbound links. Tune its severity in `docs.yml`. ## Common errors Errors below are surfaced by `fern check` and `fern generate --docs`. ### Page "X" was moved from "/old" to "/new". The old URL will return 404 without a redirect. Consider adding a redirect in docs.yml to preserve existing links. A page's [slug](/learn/docs/seo/configuring-slugs) changed relative to the last [published version](/learn/docs/preview-publish/publishing-your-docs) of your docs. Add a [redirect](/learn/docs/configuration/site-level-settings#redirects-configuration) in `docs.yml` so existing links keep working: **`docs.yml`** ```yaml title="docs.yml" redirects: - source: /old destination: /new ``` ### Page "X" was removed. The previously published URL "/old" will return 404 without a redirect. Consider adding a redirect in docs.yml to preserve existing links. A [published page](/learn/docs/preview-publish/publishing-your-docs) no longer exists in the [navigation](/learn/docs/configuration/navigation). Add a [redirect](/learn/docs/configuration/site-level-settings#redirects-configuration) to another relevant page to avoid breaking incoming links: **`docs.yml`** ```yaml title="docs.yml" redirects: - source: /old destination: /new-home ``` ### Redirect from "/path" to "/path" creates an infinite loop (source equals destination). A [redirect](/learn/docs/configuration/site-level-settings#redirects-configuration) points at itself — a request to `/path` would be redirected to `/path`, and so on forever. Either delete the redirect (if `/path` is a valid page) or point `destination` at a different URL. **`docs.yml`** ```yaml title="docs.yml" redirects: - source: /path destination: /new-path # must differ from `source` ``` ### Failed to load redirects: /path/to/redirects.yml does not exist `redirects` in `docs.yml` points at a file that isn't on disk. Filepaths resolve relative to `docs.yml`, so `redirects: ./redirects.yml` refers to a file that sits next to it. ### Failed to parse /path/to/redirects.yml: the file must nest the list under a top-level `redirects` key A redirects file holds a `redirects` key, not a bare list: **`redirects.yml`** ```yaml title="redirects.yml" redirects: - source: /old destination: /new ``` ### Circular redirect chain detected: /a → /b → /a Two or more [redirects](/learn/docs/configuration/site-level-settings#redirects-configuration) form a cycle: `/a` redirects to `/b`, and `/b` redirects back to `/a` (directly or through more hops). The browser would bounce between them indefinitely. Point every `source` in the chain at the final destination directly, so no `destination` is itself another `source`. **`docs.yml`** ```yaml title="docs.yml" redirects: - source: /a destination: /c # skip `/b` and go straight to the final page - source: /b destination: /c ``` > Learn how to configure redirects in Fern Docs. Set up exact path redirects and regex patterns to preserve SEO equity when pages move.