Skip to content

Commit

Permalink
feat(pages): add support for missing SEO front matter + improve SEO d…
Browse files Browse the repository at this point in the history
…ocs (#9071)

Co-authored-by: Thad Guidry <thadguidry@gmail.com>
  • Loading branch information
slorber and thadguidry committed Jun 15, 2023
1 parent 117cbac commit 9866af7
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 22 deletions.
15 changes: 10 additions & 5 deletions packages/docusaurus-plugin-content-pages/src/frontMatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ import {
validateFrontMatter,
FrontMatterTOCHeadingLevels,
ContentVisibilitySchema,
URISchema,
} 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>({
title: Joi.string(),
description: Joi.string(),
const PageFrontMatterSchema = Joi.object<PageFrontMatter>({
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
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(),
hide_table_of_contents: Joi.boolean(),
...FrontMatterTOCHeadingLevels,
}).concat(ContentVisibilitySchema);

export function validatePageFrontMatter(frontMatter: {
[key: string]: unknown;
}): FrontMatter {
}): PageFrontMatter {
return validateFrontMatter(frontMatter, PageFrontMatterSchema);
}
10 changes: 10 additions & 0 deletions packages/docusaurus-plugin-content-pages/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type {
PluginOptions,
Metadata,
LoadedContent,
PageFrontMatter,
} from '@docusaurus/plugin-content-pages';

export function getContentPathList(contentPaths: PagesContentPaths): string[] {
Expand Down Expand Up @@ -234,6 +235,15 @@ export default function pluginContentPages(
`${docuHash(aliasedSource)}.json`,
);
},
// Assets allow to convert some relative images paths to
// require(...) calls
createAssets: ({
frontMatter,
}: {
frontMatter: PageFrontMatter;
}) => ({
image: frontMatter.image,
}),
markdownConfig: siteConfig.markdown,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ declare module '@docusaurus/plugin-content-pages' {
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {LoadContext, Plugin} from '@docusaurus/types';

export type Assets = {
image?: string;
};

export type PluginOptions = MDXOptions & {
id?: string;
path: string;
Expand All @@ -20,9 +24,11 @@ declare module '@docusaurus/plugin-content-pages' {

export type Options = Partial<PluginOptions>;

export type FrontMatter = {
export type PageFrontMatter = {
readonly title?: string;
readonly description?: string;
readonly image?: string;
readonly keywords?: string[];
readonly wrapperClassName?: string;
readonly hide_table_of_contents?: string;
readonly toc_min_heading_level?: number;
Expand All @@ -41,7 +47,7 @@ declare module '@docusaurus/plugin-content-pages' {
type: 'mdx';
permalink: string;
source: string;
frontMatter: FrontMatter & {[key: string]: unknown};
frontMatter: PageFrontMatter & {[key: string]: unknown};
title?: string;
description?: string;
unlisted: boolean;
Expand All @@ -61,11 +67,16 @@ declare module '@theme/MDXPage' {
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
import type {
MDXPageMetadata,
FrontMatter,
PageFrontMatter,
Assets,
} from '@docusaurus/plugin-content-pages';

export interface Props {
readonly content: LoadedMDXContent<FrontMatter, MDXPageMetadata>;
readonly content: LoadedMDXContent<
PageFrontMatter,
MDXPageMetadata,
Assets
>;
}

export default function MDXPage(props: Props): JSX.Element;
Expand Down
16 changes: 13 additions & 3 deletions packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,28 @@ export default function MDXPage(props: Props): JSX.Element {
const {content: MDXPageContent} = props;
const {
metadata: {title, description, frontMatter, unlisted},
assets,
} = MDXPageContent;
const {wrapperClassName, hide_table_of_contents: hideTableOfContents} =
frontMatter;
const {
keywords,
wrapperClassName,
hide_table_of_contents: hideTableOfContents,
} = frontMatter;
const image = assets.image ?? frontMatter.image;

return (
<HtmlClassNameProvider
className={clsx(
wrapperClassName ?? ThemeClassNames.wrapper.mdxPages,
ThemeClassNames.page.mdxPage,
)}>
<PageMetadata title={title} description={description} />
<Layout>
<PageMetadata
title={title}
description={description}
keywords={keywords}
image={image}
/>
<main className="container container--fluid margin-vert--lg">
<div className={clsx('row', styles.mdxPageWrapper)}>
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
Expand Down
Binary file added website/_dogfooding/_pages tests/local-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions website/_dogfooding/_pages tests/seo.md
Original file line number Diff line number Diff line change
@@ -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
```
2 changes: 1 addition & 1 deletion website/docs/api/plugins/plugin-content-blog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ const config = {

## 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:

Expand Down
2 changes: 1 addition & 1 deletion website/docs/api/plugins/plugin-content-docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ const config = {

## 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:

Expand Down
6 changes: 4 additions & 2 deletions website/docs/api/plugins/plugin-content-pages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const config = {

## 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:

Expand All @@ -93,7 +93,9 @@ Accepted fields:
| --- | --- | --- | --- |
| `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. |
| `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. |
| `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. |
Expand Down
10 changes: 10 additions & 0 deletions website/docs/guides/markdown-features/markdown-features-intro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Markdown quotes are beautifully styled:
Expand Down
71 changes: 65 additions & 6 deletions website/docs/seo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,40 @@ Docusaurus supports search engine optimization in a variety of ways.

## 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"
module.exports = {
themeConfig: {
metadata: [{name: 'keywords', content: 'cooking, blog'}],
// This would become <meta name="keywords" content="cooking, blog"> in the generated HTML
// Declare some <meta> tags
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',
}),
},
],
},
};
```
Expand All @@ -37,13 +64,24 @@ Similar to [global metadata](#global-metadata), Docusaurus also allows for the a
# A cooking guide

<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>

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
---
Expand All @@ -58,7 +96,17 @@ When creating your React page, adding these fields in `Layout` would also improv

:::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)

:::

Expand All @@ -74,6 +122,17 @@ export default function page() {
<Layout title="Page" description="A React page demo">
<Head>
<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>
{/* ... */}
</Layout>
Expand Down

0 comments on commit 9866af7

Please sign in to comment.