diff --git a/docs/en/guide/custom-theme.md b/docs/en/guide/custom-theme.md
index 21cf4e26..57b6e695 100644
--- a/docs/en/guide/custom-theme.md
+++ b/docs/en/guide/custom-theme.md
@@ -32,6 +32,11 @@ interface Theme {
* @required
*/
Layout: Component
+ /**
+ * Content of the not-found page when the site has no `404.md`
+ * @optional
+ */
+ NotFound?: Component
/**
* Enhance Vue app instance
* @optional
@@ -130,9 +135,21 @@ The most basic layout component needs to contain a [``](../reference/
```
-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, `` 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}
Custom Layout!
-
- Custom 404 page!
-
Custom home page!
@@ -179,20 +191,18 @@ const { page, frontmatter } = useData()
You can, of course, split the layout into more components:
-```vue{3-5,12-15}
+```vue{3-4,12-13}
Custom Layout!
-
diff --git a/docs/en/guide/extending-default-theme.md b/docs/en/guide/extending-default-theme.md
index 1758d19b..733a872d 100644
--- a/docs/en/guide/extending-default-theme.md
+++ b/docs/en/guide/extending-default-theme.md
@@ -240,8 +240,6 @@ Full list of slots available in the default theme layout:
- When `layout: 'page'` is enabled via frontmatter:
- `page-top`
- `page-bottom`
-- On not found (404) page:
- - `not-found`
- Always:
- `layout-top`
- `layout-bottom`
diff --git a/docs/en/guide/i18n.md b/docs/en/guide/i18n.md
index f7f285e6..f18e3915 100644
--- a/docs/en/guide/i18n.md
+++ b/docs/en/guide/i18n.md
@@ -102,6 +102,8 @@ docs/
├─ 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:
```
diff --git a/docs/en/guide/routing.md b/docs/en/guide/routing.md
index c858ec13..ae87cfcf 100644
--- a/docs/en/guide/routing.md
+++ b/docs/en/guide/routing.md
@@ -151,6 +151,28 @@ If, however, you cannot configure your server with such support, you will have t
└─ 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
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:
diff --git a/docs/en/reference/default-theme-config.md b/docs/en/reference/default-theme-config.md
index 33760375..d32f4291 100644
--- a/docs/en/reference/default-theme-config.md
+++ b/docs/en/reference/default-theme-config.md
@@ -431,6 +431,61 @@ export interface DocFooter {
}
```
+## notFound
+
+- Type: `NotFoundOptions`
+
+Customizes the text of the not-found page. Set it under `locales..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
- Type: `string`
@@ -521,6 +576,7 @@ Returns layout-related data. The returned object has the following type:
```ts
interface {
+ layout: ComputedRef
isHome: ComputedRef
sidebar: Readonly>
diff --git a/docs/en/reference/runtime-api.md b/docs/en/reference/runtime-api.md
index 86d9a20b..a45f0e69 100644
--- a/docs/en/reference/runtime-api.md
+++ b/docs/en/reference/runtime-api.md
@@ -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.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:**
```vue
diff --git a/docs/en/reference/site-config.md b/docs/en/reference/site-config.md
index 7f214127..01637d3a 100644
--- a/docs/en/reference/site-config.md
+++ b/docs/en/reference/site-config.md
@@ -722,7 +722,7 @@ In many cases, using the [`transformPageData`](#transformpagedata) hook is a cle
```ts
export default {
async transformHead(context) {
- if (context.page === '404.md') {
+ if (context.pageData.isNotFound) {
return
}