summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Swithinbank2023-06-22 18:22:54 +0200
committerGitHub2023-06-22 18:22:54 +0200
commit1aa2187944dde4419e523f0087139f5a21efd826 (patch)
treeaf603e5fb667bc103609ef6b3b7090e43f127bff
parent4279d7512a8261b576056471f5aa1ede1e6aae4a (diff)
downloadIT.starlight-1aa2187944dde4419e523f0087139f5a21efd826.tar.gz
IT.starlight-1aa2187944dde4419e523f0087139f5a21efd826.tar.bz2
IT.starlight-1aa2187944dde4419e523f0087139f5a21efd826.zip
Support custom 404 pages (#226)
Co-authored-by: Josh Pollara <75546+joshpollara@users.noreply.github.com>
-rw-r--r--.changeset/tiny-sheep-raise.md5
-rw-r--r--docs/src/content/docs/404.md13
-rw-r--r--docs/src/content/docs/guides/customization.mdx29
-rw-r--r--packages/starlight/404.astro83
-rw-r--r--packages/starlight/components/EmptyMarkdown.md0
-rw-r--r--packages/starlight/index.astro113
-rw-r--r--packages/starlight/layout/Page.astro116
-rw-r--r--packages/starlight/schemas/i18n.ts4
-rw-r--r--packages/starlight/translations/de.json3
-rw-r--r--packages/starlight/translations/en.json3
-rw-r--r--packages/starlight/translations/es.json3
-rw-r--r--packages/starlight/translations/fr.json3
-rw-r--r--packages/starlight/translations/it.json3
-rw-r--r--packages/starlight/translations/ja.json3
-rw-r--r--packages/starlight/translations/pt.json3
15 files changed, 216 insertions, 168 deletions
diff --git a/.changeset/tiny-sheep-raise.md b/.changeset/tiny-sheep-raise.md
new file mode 100644
index 00000000..07dfedd6
--- /dev/null
+++ b/.changeset/tiny-sheep-raise.md
@@ -0,0 +1,5 @@
+---
+"@astrojs/starlight": minor
+---
+
+Add support for custom 404 pages.
diff --git a/docs/src/content/docs/404.md b/docs/src/content/docs/404.md
new file mode 100644
index 00000000..6772acfb
--- /dev/null
+++ b/docs/src/content/docs/404.md
@@ -0,0 +1,13 @@
+---
+title: Not found
+template: splash
+editUrl: false
+hero:
+ title: '404'
+ tagline: <strong>Houston, we have a problem.</strong> We couldn’t find that page.<br>Check the URL or try using the search bar.
+ actions:
+ - text: Go home
+ icon: right-arrow
+ link: /
+ variant: primary
+---
diff --git a/docs/src/content/docs/guides/customization.mdx b/docs/src/content/docs/guides/customization.mdx
index d7d3e5bd..b39c6815 100644
--- a/docs/src/content/docs/guides/customization.mdx
+++ b/docs/src/content/docs/guides/customization.mdx
@@ -236,6 +236,35 @@ export default defineConfig({
});
```
+## Custom 404 page
+
+Starlight sites display a simple 404 page by default.
+You can customize this by adding a `404.md` (or `404.mdx`) file to your `src/content/docs/` directory:
+
+<FileTree>
+
+- src/
+ - content/
+ - docs/
+ - **404.md**
+ - index.md
+- astro.config.mjs
+
+</FileTree>
+
+You can use all of Starlight’s page layout and customization techniques in your 404 page. For example, the default 404 page uses the [`splash` template](#page-layout) and [`hero`](/reference/frontmatter/#hero) component in frontmatter:
+
+```md
+---
+title: '404'
+template: splash
+editUrl: false
+hero:
+ title: '404'
+ tagline: Page not found. Check the URL or try using the search bar.
+---
+```
+
## Custom CSS styles
Customize the styles applied to your Starlight site by providing additional CSS files to modify or extend Starlight’s default styles.
diff --git a/packages/starlight/404.astro b/packages/starlight/404.astro
index 46cf5189..ecd95447 100644
--- a/packages/starlight/404.astro
+++ b/packages/starlight/404.astro
@@ -1,58 +1,39 @@
---
+import { getEntry } from 'astro:content';
import config from 'virtual:starlight/user-config';
-import { pathWithBase } from './utils/base';
+import EmptyContent from './components/EmptyMarkdown.md';
+import Page from './layout/Page.astro';
+import type { StarlightDocsEntry } from './utils/routing';
+import { useTranslations } from './utils/translations';
-// Built-in CSS styles.
-import './style/props.css';
-import './style/reset.css';
-import './style/shiki.css';
-import './style/util.css';
-
-// Layout
-import PageFrame from './layout/PageFrame.astro';
-
-// Components
-import Header from './components/Header.astro';
-import MarkdownContent from './components/MarkdownContent.astro';
-import ThemeProvider from './components/ThemeProvider.astro';
-import SkipLink from './components/SkipLink.astro';
+const { lang = 'en', dir = 'ltr', locale } = config.defaultLocale || {};
+const entryMeta = { dir, lang, locale };
+const t = useTranslations(locale);
-// Important that this is the last import so it can override built-in styles.
-import 'virtual:starlight/user-css';
+const fallbackEntry: StarlightDocsEntry = {
+ slug: '404',
+ id: '404.md' as StarlightDocsEntry['id'],
+ body: '',
+ collection: 'docs',
+ data: {
+ title: '404',
+ template: 'splash',
+ editUrl: false,
+ head: [],
+ hero: { tagline: t('404.text'), actions: [] },
+ },
+ render: async () => ({
+ Content: EmptyContent,
+ headings: [],
+ remarkPluginFrontmatter: {},
+ }),
+};
-const { lang = 'en', dir = 'ltr', locale } = config.defaultLocale || {};
+const userEntry = await getEntry('docs', '404');
+const entry = userEntry || fallbackEntry;
+const { Content, headings } = await entry.render();
---
-<html lang={lang} dir={dir}>
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width" />
- <title>Not found</title>
- </head>
- <body>
- <ThemeProvider />
- <SkipLink {locale} />
- <PageFrame {locale} hasSidebar={false}>
- <Header slot="header" {locale} />
- <main>
- <MarkdownContent>
- <h1 id="_top" data-page-title>404</h1>
- <p>Houston, we have a problem.</p>
- <p>
- We couldn’t find that link. Check the address or
- <a href={pathWithBase('/')}>head back home</a>.
- </p>
- </MarkdownContent>
- </main>
- </PageFrame>
-
- <style>
- main {
- margin: auto;
- padding: clamp(2rem, 10vmin, 6rem) var(--sl-nav-pad-x);
- max-width: var(--sl-content-width);
- max-width: max-content;
- }
- </style>
- </body>
-</html>
+<Page {headings} entry={entry} slug={entry.slug} {...entryMeta} {entryMeta}>
+ <Content />
+</Page>
diff --git a/packages/starlight/components/EmptyMarkdown.md b/packages/starlight/components/EmptyMarkdown.md
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/packages/starlight/components/EmptyMarkdown.md
diff --git a/packages/starlight/index.astro b/packages/starlight/index.astro
index 6b5193aa..65c19d85 100644
--- a/packages/starlight/index.astro
+++ b/packages/starlight/index.astro
@@ -1,122 +1,15 @@
---
import type { InferGetStaticPropsType } from 'astro';
-import config from 'virtual:starlight/user-config';
-
-import { getSidebar } from './utils/navigation';
import { paths } from './utils/routing';
-// Built-in CSS styles.
-import './style/props.css';
-import './style/reset.css';
-import './style/shiki.css';
-import './style/util.css';
-
-// Components — can override built-in CSS, but not user CSS.
-import ContentPanel from './components/ContentPanel.astro';
-import FallbackContentNotice from './components/FallbackContentNotice.astro';
-import HeadSEO from './components/HeadSEO.astro';
-import Header from './components/Header.astro';
-import Footer from './components/Footer.astro';
-import MarkdownContent from './components/MarkdownContent.astro';
-import RightSidebar from './components/RightSidebar.astro';
-import Sidebar from './components/Sidebar.astro';
-import SkipLink from './components/SkipLink.astro';
-import ThemeProvider from './components/ThemeProvider.astro';
-import PageFrame from './layout/PageFrame.astro';
-import TwoColumnContent from './layout/TwoColumnContent.astro';
-import Hero from './components/Hero.astro';
-
-// Remark component CSS (needs to override `MarkdownContent.astro`)
-import './style/asides.css';
-
-// Important that this is the last import so it can override built-in styles.
-import 'virtual:starlight/user-css';
+import Page from './layout/Page.astro';
export async function getStaticPaths() {
return paths;
}
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
-
-const { dir, entry, entryMeta, isFallback, lang, locale } = Astro.props;
-const { Content, headings } = await entry.render();
-const sidebar = getSidebar(Astro.url.pathname, locale);
-
-const hasSidebar = entry.data.template !== 'splash';
-const tocConfig = !hasSidebar
- ? false
- : entry.data.tableOfContents !== undefined
- ? entry.data.tableOfContents
- : config.tableOfContents;
-const hasToC = Boolean(tocConfig);
-const hasHero = Boolean(entry.data.hero);
+const { Content, headings } = await Astro.props.entry.render();
---
-<html lang={lang} dir={dir} data-has-toc={hasToC} data-has-sidebar={hasSidebar} data-has-hero={hasHero}>
- <head>
- <HeadSEO data={entry.data} lang={lang} />
- <style>
- html:not([data-has-toc]) {
- --sl-mobile-toc-height: 0rem;
- }
- html:not([data-has-sidebar]) {
- --sl-content-width: 67.5rem;
- }
- /* Add scroll padding to ensure anchor headings aren't obscured by nav */
- html {
- /* Additional padding is needed to account for the mobile TOC */
- scroll-padding-top: calc(
- 1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height)
- );
- }
- main {
- padding-bottom: 3vh;
- }
- @media (min-width: 50em) {
- [data-has-sidebar] {
- --sl-content-inline-start: var(--sl-sidebar-width);
- }
- }
- @media (min-width: 72em) {
- html {
- scroll-padding-top: calc(1.5rem + var(--sl-nav-height));
- }
- }
- </style>
- </head>
- <body>
- <ThemeProvider />
- <SkipLink {locale} />
- <PageFrame {locale} {hasSidebar}>
- <Header slot="header" {locale} />
- {hasSidebar && <Sidebar slot="sidebar" {sidebar} {locale} />}
- <TwoColumnContent {hasToC}>
- <RightSidebar slot="right-sidebar" {headings} {locale} {tocConfig} />
- <main data-pagefind-body lang={entryMeta.lang} dir={entryMeta.dir}>
- {/* TODO: Revisit how this logic flows. */}
- {entry.data.hero ? (
- <ContentPanel>
- <Hero hero={entry.data.hero} fallbackTitle={entry.data.title} />
- <MarkdownContent><Content /></MarkdownContent>
- </ContentPanel>
- ) : (
- <ContentPanel>
- <h1
- id="_top"
- data-page-title
- style="font-size: var(--sl-text-h1); line-height: var(--sl-line-height-headings); font-weight: 600; color: var(--sl-color-white); margin-top: 1rem;"
- >
- {entry.data.title}
- </h1>
- {isFallback && <FallbackContentNotice {locale} />}
- </ContentPanel>
- <ContentPanel>
- <MarkdownContent><Content /></MarkdownContent>
- <Footer {...{ entry, dir, lang, locale, sidebar }} />
- </ContentPanel>
- )}
- </main>
- </TwoColumnContent>
- </PageFrame>
- </body>
-</html>
+<Page {...Astro.props} {headings}><Content /></Page>
diff --git a/packages/starlight/layout/Page.astro b/packages/starlight/layout/Page.astro
new file mode 100644
index 00000000..b09abe8c
--- /dev/null
+++ b/packages/starlight/layout/Page.astro
@@ -0,0 +1,116 @@
+---
+import config from 'virtual:starlight/user-config';
+import type { MarkdownHeading } from 'astro';
+import { getSidebar } from '../utils/navigation';
+import type { Route } from '../utils/routing';
+
+// Built-in CSS styles.
+import '../style/props.css';
+import '../style/reset.css';
+import '../style/shiki.css';
+import '../style/util.css';
+
+// Components — can override built-in CSS, but not user CSS.
+import ContentPanel from '../components/ContentPanel.astro';
+import FallbackContentNotice from '../components/FallbackContentNotice.astro';
+import Footer from '../components/Footer.astro';
+import HeadSEO from '../components/HeadSEO.astro';
+import Header from '../components/Header.astro';
+import Hero from '../components/Hero.astro';
+import MarkdownContent from '../components/MarkdownContent.astro';
+import RightSidebar from '../components/RightSidebar.astro';
+import Sidebar from '../components/Sidebar.astro';
+import SkipLink from '../components/SkipLink.astro';
+import ThemeProvider from '../components/ThemeProvider.astro';
+import PageFrame from '../layout/PageFrame.astro';
+import TwoColumnContent from '../layout/TwoColumnContent.astro';
+
+// Remark component CSS (needs to override `MarkdownContent.astro`)
+import '../style/asides.css';
+
+// Important that this is the last import so it can override built-in styles.
+import 'virtual:starlight/user-css';
+
+type Props = Route & { headings: MarkdownHeading[] };
+
+const { dir, entry, entryMeta, headings, isFallback, lang, locale } = Astro.props;
+const sidebar = getSidebar(Astro.url.pathname, locale);
+
+const hasSidebar = entry.data.template !== 'splash';
+const tocConfig = !hasSidebar
+ ? false
+ : entry.data.tableOfContents !== undefined
+ ? entry.data.tableOfContents
+ : config.tableOfContents;
+const hasToC = Boolean(tocConfig);
+const hasHero = Boolean(entry.data.hero);
+---
+
+<html lang={lang} dir={dir} data-has-toc={hasToC} data-has-sidebar={hasSidebar} data-has-hero={hasHero}>
+ <head>
+ <HeadSEO data={entry.data} lang={lang} />
+ <style>
+ html:not([data-has-toc]) {
+ --sl-mobile-toc-height: 0rem;
+ }
+ html:not([data-has-sidebar]) {
+ --sl-content-width: 67.5rem;
+ }
+ /* Add scroll padding to ensure anchor headings aren't obscured by nav */
+ html {
+ /* Additional padding is needed to account for the mobile TOC */
+ scroll-padding-top: calc(
+ 1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height)
+ );
+ }
+ main {
+ padding-bottom: 3vh;
+ }
+ @media (min-width: 50em) {
+ [data-has-sidebar] {
+ --sl-content-inline-start: var(--sl-sidebar-width);
+ }
+ }
+ @media (min-width: 72em) {
+ html {
+ scroll-padding-top: calc(1.5rem + var(--sl-nav-height));
+ }
+ }
+ </style>
+ </head>
+ <body>
+ <ThemeProvider />
+ <SkipLink {locale} />
+ <PageFrame {locale} {hasSidebar}>
+ <Header slot="header" {locale} />
+ {hasSidebar && <Sidebar slot="sidebar" {sidebar} {locale} />}
+ <TwoColumnContent {hasToC}>
+ <RightSidebar slot="right-sidebar" {headings} {locale} {tocConfig} />
+ <main data-pagefind-body={entry.slug !== '404'} lang={entryMeta.lang} dir={entryMeta.dir}>
+ {/* TODO: Revisit how this logic flows. */}
+ {entry.data.hero ? (
+ <ContentPanel>
+ <Hero hero={entry.data.hero} fallbackTitle={entry.data.title} />
+ <MarkdownContent><slot /></MarkdownContent>
+ </ContentPanel>
+ ) : (
+ <ContentPanel>
+ <h1
+ id="_top"
+ data-page-title
+ style="font-size: var(--sl-text-h1); line-height: var(--sl-line-height-headings); font-weight: 600; color: var(--sl-color-white); margin-top: 1rem;"
+ >
+ {entry.data.title}
+ </h1>
+ {isFallback && <FallbackContentNotice {locale} />}
+ </ContentPanel>
+ <ContentPanel>
+ <MarkdownContent><slot /></MarkdownContent>
+ <Footer {...{ entry, dir, lang, locale, sidebar }} />
+ </ContentPanel>
+ )}
+ </main>
+ </TwoColumnContent>
+ </PageFrame>
+ </body>
+</html> \ No newline at end of file
diff --git a/packages/starlight/schemas/i18n.ts b/packages/starlight/schemas/i18n.ts
index be0aea70..4bac4bec 100644
--- a/packages/starlight/schemas/i18n.ts
+++ b/packages/starlight/schemas/i18n.ts
@@ -98,6 +98,10 @@ function starlightI18nSchema() {
.describe(
'Label shown on the “next page” pagination arrow in the page footer.'
),
+
+ '404.text': z
+ .string()
+ .describe('Text shown on Starlight’s default 404 page'),
})
.partial();
}
diff --git a/packages/starlight/translations/de.json b/packages/starlight/translations/de.json
index fee8b961..da82f791 100644
--- a/packages/starlight/translations/de.json
+++ b/packages/starlight/translations/de.json
@@ -17,5 +17,6 @@
"page.editLink": "Seite bearbeiten",
"page.lastUpdated": "Zuletzt bearbeitet:",
"page.previousLink": "Vorherige Seite",
- "page.nextLink": "Nächste Seite"
+ "page.nextLink": "Nächste Seite",
+ "404.text": "Seite nicht gefunden. Überprüfe die URL oder nutze die Suchleiste."
}
diff --git a/packages/starlight/translations/en.json b/packages/starlight/translations/en.json
index 4bf800a5..5bc396b2 100644
--- a/packages/starlight/translations/en.json
+++ b/packages/starlight/translations/en.json
@@ -17,5 +17,6 @@
"page.editLink": "Edit page",
"page.lastUpdated": "Last updated:",
"page.previousLink": "Previous",
- "page.nextLink": "Next"
+ "page.nextLink": "Next",
+ "404.text": "Page not found. Check the URL or try using the search bar."
}
diff --git a/packages/starlight/translations/es.json b/packages/starlight/translations/es.json
index e03ae58d..8adbe302 100644
--- a/packages/starlight/translations/es.json
+++ b/packages/starlight/translations/es.json
@@ -17,5 +17,6 @@
"page.editLink": "Edita esta página",
"page.lastUpdated": "Última actualización:",
"page.previousLink": "Página anterior",
- "page.nextLink": "Siguiente página"
+ "page.nextLink": "Siguiente página",
+ "404.text": "Página no encontrada. Verifique la URL o intente usar la barra de búsqueda."
}
diff --git a/packages/starlight/translations/fr.json b/packages/starlight/translations/fr.json
index a1c437b7..345cb05c 100644
--- a/packages/starlight/translations/fr.json
+++ b/packages/starlight/translations/fr.json
@@ -17,5 +17,6 @@
"page.editLink": "Editer la page",
"page.lastUpdated": "Dernière mise à jour :",
"page.previousLink": "Précédent",
- "page.nextLink": "Suivant"
+ "page.nextLink": "Suivant",
+ "404.text": "Page non trouvée. Vérifiez l'URL ou essayez d'utiliser la barre de recherche."
}
diff --git a/packages/starlight/translations/it.json b/packages/starlight/translations/it.json
index b9991267..0560fb90 100644
--- a/packages/starlight/translations/it.json
+++ b/packages/starlight/translations/it.json
@@ -17,5 +17,6 @@
"page.editLink": "Modifica pagina",
"page.lastUpdated": "Ultimo aggiornamento:",
"page.previousLink": "Indietro",
- "page.nextLink": "Avanti"
+ "page.nextLink": "Avanti",
+ "404.text": "Pagina non trovata. Verifica l'URL o prova a utilizzare la barra di ricerca."
}
diff --git a/packages/starlight/translations/ja.json b/packages/starlight/translations/ja.json
index 75e2b06e..1f199b48 100644
--- a/packages/starlight/translations/ja.json
+++ b/packages/starlight/translations/ja.json
@@ -17,5 +17,6 @@
"page.editLink": "ページを編集",
"page.lastUpdated": "最終更新日:",
"page.previousLink": "前へ",
- "page.nextLink": "次へ"
+ "page.nextLink": "次へ",
+ "404.text": "ページが見つかりません。 URL を確認するか、検索バーを使用してみてください。"
}
diff --git a/packages/starlight/translations/pt.json b/packages/starlight/translations/pt.json
index 17908cd1..2b379035 100644
--- a/packages/starlight/translations/pt.json
+++ b/packages/starlight/translations/pt.json
@@ -17,5 +17,6 @@
"page.editLink": "Editar página",
"page.lastUpdated": "Última atualização:",
"page.previousLink": "Anterior",
- "page.nextLink": "Próximo"
+ "page.nextLink": "Próximo",
+ "404.text": "Página não encontrada. Verifique o URL ou tente usar a barra de pesquisa."
}