docs: describe the not-found page

Adds a routing guide section for `404.md` and per-locale pages, documents
`themeConfig.notFound` and `Theme.NotFound`, and updates the custom theme,
default theme, i18n, site config and runtime API pages accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
feat/not-found
Divyansh Singh 1 week ago
parent 10e1780dcc
commit 19062c3df0

@ -32,6 +32,11 @@ interface Theme {
* @required * @required
*/ */
Layout: Component Layout: Component
/**
* Content of the not-found page when the site has no `404.md`
* @optional
*/
NotFound?: Component
/** /**
* Enhance Vue app instance * Enhance Vue app instance
* @optional * @optional
@ -130,9 +135,21 @@ The most basic layout component needs to contain a [`<Content />`](../reference/
</template> </template>
``` ```
The above layout simply renders every page's markdown as HTML. The first improvement we can add is to handle 404 errors: The above layout renders every page's markdown as HTML. That includes the not-found page: when a visitor opens a URL that has no page, `<Content />` renders the site's `404.md`, or the theme's `NotFound` component when the site has none. A theme should ship that component, so every site gets a not-found page without writing one. Without it, a small unstyled built-in page is shown instead.
```js [.vitepress/theme/index.js]
import Layout from './Layout.vue'
import NotFound from './NotFound.vue'
export default {
Layout,
NotFound
}
```
The [`useData()`](../reference/runtime-api#usedata) helper provides us with all the runtime data we need to conditionally render different layouts. For example, `page.isNotFound` is `true` on the not-found page, so the layout can leave out the parts that only make sense for real pages:
```vue{1-4,9-12} ```vue{1-4,9}
<script setup> <script setup>
import { useData } from 'vitepress' import { useData } from 'vitepress'
const { page } = useData() const { page } = useData()
@ -141,14 +158,12 @@ const { page } = useData()
<template> <template>
<h1>Custom Layout!</h1> <h1>Custom Layout!</h1>
<div v-if="page.isNotFound"> <aside v-if="!page.isNotFound">Table of contents</aside>
Custom 404 page! <Content />
</div>
<Content v-else />
</template> </template>
``` ```
The [`useData()`](../reference/runtime-api#usedata) helper provides us with all the runtime data we need to conditionally render different layouts. One of the other data we can access is the current page's frontmatter. We can leverage this to allow the end user to control the layout in each page. For example, the user can indicate the page should use a special home page layout with: One of the other data we can access is the current page's frontmatter. We can leverage this to allow the end user to control the layout in each page. For example, the user can indicate the page should use a special home page layout with:
```md ```md
--- ---
@ -158,18 +173,15 @@ layout: home
And we can adjust our theme to handle this: And we can adjust our theme to handle this:
```vue{3,12-14} ```vue{3,9-12}
<script setup> <script setup>
import { useData } from 'vitepress' import { useData } from 'vitepress'
const { page, frontmatter } = useData() const { frontmatter } = useData()
</script> </script>
<template> <template>
<h1>Custom Layout!</h1> <h1>Custom Layout!</h1>
<div v-if="page.isNotFound">
Custom 404 page!
</div>
<div v-if="frontmatter.layout === 'home'"> <div v-if="frontmatter.layout === 'home'">
Custom home page! Custom home page!
</div> </div>
@ -179,20 +191,18 @@ const { page, frontmatter } = useData()
You can, of course, split the layout into more components: You can, of course, split the layout into more components:
```vue{3-5,12-15} ```vue{3-4,12-13}
<script setup> <script setup>
import { useData } from 'vitepress' import { useData } from 'vitepress'
import NotFound from './NotFound.vue'
import Home from './Home.vue' import Home from './Home.vue'
import Page from './Page.vue' import Page from './Page.vue'
const { page, frontmatter } = useData() const { frontmatter } = useData()
</script> </script>
<template> <template>
<h1>Custom Layout!</h1> <h1>Custom Layout!</h1>
<NotFound v-if="page.isNotFound" />
<Home v-if="frontmatter.layout === 'home'" /> <Home v-if="frontmatter.layout === 'home'" />
<Page v-else /> <!-- <Page /> renders <Content /> --> <Page v-else /> <!-- <Page /> renders <Content /> -->
</template> </template>

@ -240,8 +240,6 @@ Full list of slots available in the default theme layout:
- When `layout: 'page'` is enabled via frontmatter: - When `layout: 'page'` is enabled via frontmatter:
- `page-top` - `page-top`
- `page-bottom` - `page-bottom`
- On not found (404) page:
- `not-found`
- Always: - Always:
- `layout-top` - `layout-top`
- `layout-bottom` - `layout-bottom`

@ -102,6 +102,8 @@ docs/
├─ foo.md ├─ foo.md
``` ```
Each locale directory can also have its own [`404.md`](./routing#not-found-page). A locale without one shares the root `404.md`.
However, VitePress won't redirect `/` to `/en/` by default. You'll need to configure your server for that. For example, on Netlify, you can add a `docs/public/_redirects` file like this: However, VitePress won't redirect `/` to `/en/` by default. You'll need to configure your server for that. For example, on Netlify, you can add a `docs/public/_redirects` file like this:
``` ```

@ -151,6 +151,28 @@ If, however, you cannot configure your server with such support, you will have t
└─ index.md └─ index.md
``` ```
## Not Found Page
When a visitor opens a URL that has no page, VitePress shows the not-found page. The default theme ships one, and you can change its text with the [`notFound`](../reference/default-theme-config#notfound) theme option.
To replace the page entirely, add a `404.md` file to your source directory. It is a regular page: frontmatter, Markdown and Vue components all work.
```md [404.md]
---
title: Page not found
---
# Page not found
The page you are looking for does not exist. [Go to the homepage](/).
```
With [multiple locales](./i18n), each locale directory can have its own `404.md`, for example `zh/404.md`. A locale without one uses the root `404.md`, and the theme's default page when there is none either.
The build emits `404.html` at the output root and one in each locale directory. Most hosts pick up `404.html` automatically, see the [deployment guide](./deploy). The dev and preview servers answer a miss with a real 404 status too.
On the not-found page, `useData().page.isNotFound` is `true` and `useRoute().path` holds the URL the visitor asked for. The page is left out of the sitemap and the local search index.
## Route Rewrites ## Route Rewrites
You can customize the mapping between the source directory structure and the generated pages. It's useful when you have a complex project structure. For example, let's say you have a monorepo with multiple packages, and would like to place documentations along with the source files like this: You can customize the mapping between the source directory structure and the generated pages. It's useful when you have a complex project structure. For example, let's say you have a monorepo with multiple packages, and would like to place documentations along with the source files like this:

@ -431,6 +431,61 @@ export interface DocFooter {
} }
``` ```
## notFound
- Type: `NotFoundOptions`
Customizes the text of the not-found page. Set it under `locales.<locale>.themeConfig` to translate it. To replace the whole page, add a [`404.md`](../guide/routing#not-found-page) to your site instead.
```ts
export interface NotFoundOptions {
/**
* Set custom not found message.
*
* @default 'PAGE NOT FOUND'
*/
title?: string
/**
* Set custom not found description.
*
* @default "But if you don't change your direction, and if you keep looking, you may end up where you are heading."
*/
quote?: string
/**
* Target of the home link. Defaults to the home of the current locale.
*/
link?: string
/**
* Set custom home link text.
*
* @default 'Take me home'
*/
linkText?: string
/**
* @default '404'
*/
code?: string
}
```
**Example:**
```ts
export default {
themeConfig: {
notFound: {
title: 'Nothing here',
quote: 'The page you are looking for may have moved.',
linkText: 'Back to the docs'
}
}
}
```
## darkModeSwitchLabel ## darkModeSwitchLabel
- Type: `string` - Type: `string`
@ -521,6 +576,7 @@ Returns layout-related data. The returned object has the following type:
```ts ```ts
interface { interface {
layout: ComputedRef<string>
isHome: ComputedRef<boolean> isHome: ComputedRef<boolean>
sidebar: Readonly<ShallowRef<DefaultTheme.SidebarItem[]>> sidebar: Readonly<ShallowRef<DefaultTheme.SidebarItem[]>>

@ -64,6 +64,8 @@ interface PageData {
`page.headers` is populated only when [`markdown.headers`](./site-config#markdown) is enabled. Without that option, it remains an empty array. The default theme outline reads rendered headings from the page content, so it can still appear when `page.headers` is empty. `page.headers` is populated only when [`markdown.headers`](./site-config#markdown) is enabled. Without that option, it remains an empty array. The default theme outline reads rendered headings from the page content, so it can still appear when `page.headers` is empty.
`page.isNotFound` is `true` on the [not-found page](../guide/routing#not-found-page), which also answers every URL that has no page. `useRoute().path` still holds the URL the visitor asked for.
**Example:** **Example:**
```vue ```vue

@ -722,7 +722,7 @@ In many cases, using the [`transformPageData`](#transformpagedata) hook is a cle
```ts ```ts
export default { export default {
async transformHead(context) { async transformHead(context) {
if (context.page === '404.md') { if (context.pageData.isNotFound) {
return return
} }

Loading…
Cancel
Save