mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
feat(pages): add support for missing SEO front matter + improve SEO docs (#9071)
Co-authored-by: Thad Guidry <thadguidry@gmail.com>
This commit is contained in:
parent
117cbac702
commit
9866af7f44
11 changed files with 146 additions and 22 deletions
|
@ -10,12 +10,17 @@ import {
|
||||||
validateFrontMatter,
|
validateFrontMatter,
|
||||||
FrontMatterTOCHeadingLevels,
|
FrontMatterTOCHeadingLevels,
|
||||||
ContentVisibilitySchema,
|
ContentVisibilitySchema,
|
||||||
|
URISchema,
|
||||||
} from '@docusaurus/utils-validation';
|
} from '@docusaurus/utils-validation';
|
||||||
import type {FrontMatter} from '@docusaurus/plugin-content-pages';
|
import type {PageFrontMatter} from '@docusaurus/plugin-content-pages';
|
||||||
|
|
||||||
const PageFrontMatterSchema = Joi.object<FrontMatter>({
|
const PageFrontMatterSchema = Joi.object<PageFrontMatter>({
|
||||||
title: Joi.string(),
|
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
|
||||||
description: Joi.string(),
|
title: Joi.string().allow(''),
|
||||||
|
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
|
||||||
|
description: Joi.string().allow(''),
|
||||||
|
keywords: Joi.array().items(Joi.string().required()),
|
||||||
|
image: URISchema,
|
||||||
wrapperClassName: Joi.string(),
|
wrapperClassName: Joi.string(),
|
||||||
hide_table_of_contents: Joi.boolean(),
|
hide_table_of_contents: Joi.boolean(),
|
||||||
...FrontMatterTOCHeadingLevels,
|
...FrontMatterTOCHeadingLevels,
|
||||||
|
@ -23,6 +28,6 @@ const PageFrontMatterSchema = Joi.object<FrontMatter>({
|
||||||
|
|
||||||
export function validatePageFrontMatter(frontMatter: {
|
export function validatePageFrontMatter(frontMatter: {
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}): FrontMatter {
|
}): PageFrontMatter {
|
||||||
return validateFrontMatter(frontMatter, PageFrontMatterSchema);
|
return validateFrontMatter(frontMatter, PageFrontMatterSchema);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import type {
|
||||||
PluginOptions,
|
PluginOptions,
|
||||||
Metadata,
|
Metadata,
|
||||||
LoadedContent,
|
LoadedContent,
|
||||||
|
PageFrontMatter,
|
||||||
} from '@docusaurus/plugin-content-pages';
|
} from '@docusaurus/plugin-content-pages';
|
||||||
|
|
||||||
export function getContentPathList(contentPaths: PagesContentPaths): string[] {
|
export function getContentPathList(contentPaths: PagesContentPaths): string[] {
|
||||||
|
@ -234,6 +235,15 @@ export default function pluginContentPages(
|
||||||
`${docuHash(aliasedSource)}.json`,
|
`${docuHash(aliasedSource)}.json`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
// Assets allow to convert some relative images paths to
|
||||||
|
// require(...) calls
|
||||||
|
createAssets: ({
|
||||||
|
frontMatter,
|
||||||
|
}: {
|
||||||
|
frontMatter: PageFrontMatter;
|
||||||
|
}) => ({
|
||||||
|
image: frontMatter.image,
|
||||||
|
}),
|
||||||
markdownConfig: siteConfig.markdown,
|
markdownConfig: siteConfig.markdown,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,10 @@ declare module '@docusaurus/plugin-content-pages' {
|
||||||
import type {MDXOptions} from '@docusaurus/mdx-loader';
|
import type {MDXOptions} from '@docusaurus/mdx-loader';
|
||||||
import type {LoadContext, Plugin} from '@docusaurus/types';
|
import type {LoadContext, Plugin} from '@docusaurus/types';
|
||||||
|
|
||||||
|
export type Assets = {
|
||||||
|
image?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type PluginOptions = MDXOptions & {
|
export type PluginOptions = MDXOptions & {
|
||||||
id?: string;
|
id?: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -20,9 +24,11 @@ declare module '@docusaurus/plugin-content-pages' {
|
||||||
|
|
||||||
export type Options = Partial<PluginOptions>;
|
export type Options = Partial<PluginOptions>;
|
||||||
|
|
||||||
export type FrontMatter = {
|
export type PageFrontMatter = {
|
||||||
readonly title?: string;
|
readonly title?: string;
|
||||||
readonly description?: string;
|
readonly description?: string;
|
||||||
|
readonly image?: string;
|
||||||
|
readonly keywords?: string[];
|
||||||
readonly wrapperClassName?: string;
|
readonly wrapperClassName?: string;
|
||||||
readonly hide_table_of_contents?: string;
|
readonly hide_table_of_contents?: string;
|
||||||
readonly toc_min_heading_level?: number;
|
readonly toc_min_heading_level?: number;
|
||||||
|
@ -41,7 +47,7 @@ declare module '@docusaurus/plugin-content-pages' {
|
||||||
type: 'mdx';
|
type: 'mdx';
|
||||||
permalink: string;
|
permalink: string;
|
||||||
source: string;
|
source: string;
|
||||||
frontMatter: FrontMatter & {[key: string]: unknown};
|
frontMatter: PageFrontMatter & {[key: string]: unknown};
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
unlisted: boolean;
|
unlisted: boolean;
|
||||||
|
@ -61,11 +67,16 @@ declare module '@theme/MDXPage' {
|
||||||
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
|
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
|
||||||
import type {
|
import type {
|
||||||
MDXPageMetadata,
|
MDXPageMetadata,
|
||||||
FrontMatter,
|
PageFrontMatter,
|
||||||
|
Assets,
|
||||||
} from '@docusaurus/plugin-content-pages';
|
} from '@docusaurus/plugin-content-pages';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly content: LoadedMDXContent<FrontMatter, MDXPageMetadata>;
|
readonly content: LoadedMDXContent<
|
||||||
|
PageFrontMatter,
|
||||||
|
MDXPageMetadata,
|
||||||
|
Assets
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MDXPage(props: Props): JSX.Element;
|
export default function MDXPage(props: Props): JSX.Element;
|
||||||
|
|
|
@ -24,9 +24,14 @@ export default function MDXPage(props: Props): JSX.Element {
|
||||||
const {content: MDXPageContent} = props;
|
const {content: MDXPageContent} = props;
|
||||||
const {
|
const {
|
||||||
metadata: {title, description, frontMatter, unlisted},
|
metadata: {title, description, frontMatter, unlisted},
|
||||||
|
assets,
|
||||||
} = MDXPageContent;
|
} = MDXPageContent;
|
||||||
const {wrapperClassName, hide_table_of_contents: hideTableOfContents} =
|
const {
|
||||||
frontMatter;
|
keywords,
|
||||||
|
wrapperClassName,
|
||||||
|
hide_table_of_contents: hideTableOfContents,
|
||||||
|
} = frontMatter;
|
||||||
|
const image = assets.image ?? frontMatter.image;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HtmlClassNameProvider
|
<HtmlClassNameProvider
|
||||||
|
@ -34,8 +39,13 @@ export default function MDXPage(props: Props): JSX.Element {
|
||||||
wrapperClassName ?? ThemeClassNames.wrapper.mdxPages,
|
wrapperClassName ?? ThemeClassNames.wrapper.mdxPages,
|
||||||
ThemeClassNames.page.mdxPage,
|
ThemeClassNames.page.mdxPage,
|
||||||
)}>
|
)}>
|
||||||
<PageMetadata title={title} description={description} />
|
|
||||||
<Layout>
|
<Layout>
|
||||||
|
<PageMetadata
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
keywords={keywords}
|
||||||
|
image={image}
|
||||||
|
/>
|
||||||
<main className="container container--fluid margin-vert--lg">
|
<main className="container container--fluid margin-vert--lg">
|
||||||
<div className={clsx('row', styles.mdxPageWrapper)}>
|
<div className={clsx('row', styles.mdxPageWrapper)}>
|
||||||
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
|
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
|
||||||
|
|
BIN
website/_dogfooding/_pages tests/local-image.png
Normal file
BIN
website/_dogfooding/_pages tests/local-image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7 KiB |
17
website/_dogfooding/_pages tests/seo.md
Normal file
17
website/_dogfooding/_pages tests/seo.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
title: custom SEO title
|
||||||
|
description: custom SEO description
|
||||||
|
keywords: [custom, keywords]
|
||||||
|
image: ./local-image.png
|
||||||
|
---
|
||||||
|
|
||||||
|
# SEO tests
|
||||||
|
|
||||||
|
Using page SEO front matter:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
title: custom SEO title
|
||||||
|
description: custom SEO description
|
||||||
|
keywords: [custom, keywords]
|
||||||
|
image: ./local-image.png
|
||||||
|
```
|
|
@ -194,7 +194,7 @@ const config = {
|
||||||
|
|
||||||
## Markdown front matter {#markdown-front-matter}
|
## Markdown front matter {#markdown-front-matter}
|
||||||
|
|
||||||
Markdown documents can use the following Markdown front matter metadata fields, enclosed by a line `---` on either side.
|
Markdown documents can use the following Markdown [front matter](../../guides/markdown-features/markdown-features-intro.mdx#front-matter) metadata fields, enclosed by a line `---` on either side.
|
||||||
|
|
||||||
Accepted fields:
|
Accepted fields:
|
||||||
|
|
||||||
|
|
|
@ -261,7 +261,7 @@ const config = {
|
||||||
|
|
||||||
## Markdown front matter {#markdown-front-matter}
|
## Markdown front matter {#markdown-front-matter}
|
||||||
|
|
||||||
Markdown documents can use the following Markdown front matter metadata fields, enclosed by a line `---` on either side.
|
Markdown documents can use the following Markdown [front matter](../../guides/markdown-features/markdown-features-intro.mdx#front-matter) metadata fields, enclosed by a line `---` on either side.
|
||||||
|
|
||||||
Accepted fields:
|
Accepted fields:
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ const config = {
|
||||||
|
|
||||||
## Markdown front matter {#markdown-front-matter}
|
## Markdown front matter {#markdown-front-matter}
|
||||||
|
|
||||||
Markdown pages can use the following Markdown front matter metadata fields, enclosed by a line `---` on either side.
|
Markdown pages can use the following Markdown [front matter](../../guides/markdown-features/markdown-features-intro.mdx#front-matter) metadata fields, enclosed by a line `---` on either side.
|
||||||
|
|
||||||
Accepted fields:
|
Accepted fields:
|
||||||
|
|
||||||
|
@ -93,7 +93,9 @@ Accepted fields:
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `title` | `string` | Markdown title | The blog post title. |
|
| `title` | `string` | Markdown title | The blog post title. |
|
||||||
| `description` | `string` | The first line of Markdown content | The description of your page, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. |
|
| `description` | `string` | The first line of Markdown content | The description of your page, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. |
|
||||||
| `wrapperClassName` | `string` | Class name to be added to the wrapper element to allow targeting specific page content. |
|
| `keywords` | `string[]` | `undefined` | Keywords meta tag, which will become the `<meta name="keywords" content="keyword1,keyword2,..."/>` in `<head>`, used by search engines. |
|
||||||
|
| `image` | `string` | `undefined` | Cover or thumbnail image that will be used when displaying the link to your post. |
|
||||||
|
| `wrapperClassName` | `string` | | Class name to be added to the wrapper element to allow targeting specific page content. |
|
||||||
| `hide_table_of_contents` | `boolean` | `false` | Whether to hide the table of contents to the right. |
|
| `hide_table_of_contents` | `boolean` | `false` | Whether to hide the table of contents to the right. |
|
||||||
| `draft` | `boolean` | `false` | Draft pages will only be available during development. |
|
| `draft` | `boolean` | `false` | Draft pages will only be available during development. |
|
||||||
| `unlisted` | `boolean` | `false` | Unlisted pages will be available in both development and production. They will be "hidden" in production, not indexed, excluded from sitemaps, and can only be accessed by users having a direct link. |
|
| `unlisted` | `boolean` | `false` | Unlisted pages will be available in both development and production. They will be "hidden" in production, not indexed, excluded from sitemaps, and can only be accessed by users having a direct link. |
|
||||||
|
|
|
@ -72,6 +72,16 @@ more_data:
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
The API documentation of each official plugin lists the supported attributes:
|
||||||
|
|
||||||
|
- [Docs front matter](../../api/plugins/plugin-content-docs.mdx#markdown-front-matter)
|
||||||
|
- [Blog front matter](../../api/plugins/plugin-content-blog.mdx#markdown-front-matter)
|
||||||
|
- [Pages front matter](../../api/plugins/plugin-content-pages.mdx#markdown-front-matter)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Quotes {#quotes}
|
## Quotes {#quotes}
|
||||||
|
|
||||||
Markdown quotes are beautifully styled:
|
Markdown quotes are beautifully styled:
|
||||||
|
|
|
@ -14,13 +14,40 @@ Docusaurus supports search engine optimization in a variety of ways.
|
||||||
|
|
||||||
## Global metadata {#global-metadata}
|
## Global metadata {#global-metadata}
|
||||||
|
|
||||||
Provide global meta attributes for the entire site through the [site configuration](./configuration.mdx#site-metadata). The metadata will all be rendered in the HTML `<head>` using the key-value pairs as the prop name and value.
|
Provide global meta attributes for the entire site through the [site configuration](./configuration.mdx#site-metadata). The metadata will all be rendered in the HTML `<head>` using the key-value pairs as the prop name and value. The `metadata` attribute is a convenient shortcut to declare `<meta>` tags, but it is also possible to inject arbitrary tags in `<head>` with the `headTags` attribute.
|
||||||
|
|
||||||
```js title="docusaurus.config.js"
|
```js title="docusaurus.config.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
metadata: [{name: 'keywords', content: 'cooking, blog'}],
|
// Declare some <meta> tags
|
||||||
// This would become <meta name="keywords" content="cooking, blog"> in the generated HTML
|
metadata: [
|
||||||
|
{name: 'keywords', content: 'cooking, blog'},
|
||||||
|
{name: 'twitter:card', content: 'summary_large_image'},
|
||||||
|
],
|
||||||
|
headTags: [
|
||||||
|
// Declare a <link> preconnect tag
|
||||||
|
{
|
||||||
|
tagName: 'link',
|
||||||
|
attributes: {
|
||||||
|
rel: 'preconnect',
|
||||||
|
href: 'https://example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Declare some json-ld structured data
|
||||||
|
{
|
||||||
|
tagName: 'script',
|
||||||
|
attributes: {
|
||||||
|
type: 'application/ld+json',
|
||||||
|
},
|
||||||
|
innerHTML: JSON.stringify({
|
||||||
|
'@context': 'https://schema.org/',
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Meta Open Source',
|
||||||
|
url: 'https://opensource.fb.com/',
|
||||||
|
logo: 'https://opensource.fb.com/img/logos/Meta-Open-Source.svg',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
@ -37,13 +64,24 @@ Similar to [global metadata](#global-metadata), Docusaurus also allows for the a
|
||||||
# A cooking guide
|
# A cooking guide
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta name="keywords" content="cooking, blog">
|
<meta name="keywords" content="cooking, blog" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<link rel="preconnect" href="https://example.com" />
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{JSON.stringify({
|
||||||
|
'@context': 'https://schema.org/',
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Meta Open Source',
|
||||||
|
url: 'https://opensource.fb.com/',
|
||||||
|
logo: 'https://opensource.fb.com/img/logos/Meta-Open-Source.svg',
|
||||||
|
})}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
Some content...
|
Some content...
|
||||||
```
|
```
|
||||||
|
|
||||||
Docusaurus automatically adds `description`, `title`, canonical URL links, and other useful metadata to each Markdown page. They are configurable through front matter:
|
Docusaurus automatically adds `description`, `title`, canonical URL links, and other useful metadata to each Markdown page. They are configurable through [front matter](./guides/markdown-features/markdown-features-intro.mdx#front-matter):
|
||||||
|
|
||||||
```md
|
```md
|
||||||
---
|
---
|
||||||
|
@ -58,7 +96,17 @@ When creating your React page, adding these fields in `Layout` would also improv
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|
||||||
Prefer to use front matter for fields like `description` and `keywords`: Docusaurus will automatically apply this to both `description` and `og:description`, while you would have to manually declare two metadata tags when using the `<head>` tag.
|
Prefer to use [front matter](./guides/markdown-features/markdown-features-intro.mdx#front-matter) for fields like `description` and `keywords`: Docusaurus will automatically apply this to both `description` and `og:description`, while you would have to manually declare two metadata tags when using the `<head>` tag.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
The official plugins all support the following [front matter](./guides/markdown-features/markdown-features-intro.mdx#front-matter): `title`, `description`, `keywords` and `image`. Refer to their respective API documentation for additional [front matter](./guides/markdown-features/markdown-features-intro.mdx#front-matter) support:
|
||||||
|
|
||||||
|
- [Docs front matter](./api/plugins/plugin-content-docs.mdx#markdown-front-matter)
|
||||||
|
- [Blog front matter](./api/plugins/plugin-content-blog.mdx#markdown-front-matter)
|
||||||
|
- [Pages front matter](./api/plugins/plugin-content-pages.mdx#markdown-front-matter)
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
@ -74,6 +122,17 @@ export default function page() {
|
||||||
<Layout title="Page" description="A React page demo">
|
<Layout title="Page" description="A React page demo">
|
||||||
<Head>
|
<Head>
|
||||||
<meta property="og:image" content="image.png" />
|
<meta property="og:image" content="image.png" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<link rel="preconnect" href="https://example.com" />
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{JSON.stringify({
|
||||||
|
'@context': 'https://schema.org/',
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Meta Open Source',
|
||||||
|
url: 'https://opensource.fb.com/',
|
||||||
|
logo: 'https://opensource.fb.com/img/logos/Meta-Open-Source.svg',
|
||||||
|
})}
|
||||||
|
</script>
|
||||||
</Head>
|
</Head>
|
||||||
{/* ... */}
|
{/* ... */}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue