feat: add metatags support for seo / blogposts #5373 (#5375)

* feat: add metatags support for seo / blogposts

* feat: different implementation

* feat: use isBlogPostPage

* feat: implement in BlogPostPage-remove Seo changes

* Revert "feat: implement in BlogPostPage-remove Seo changes"

This reverts commit 1cba459b

* Move Seo to BlogPostPage + some fixes

* fix social preview asset

* Fix blog social image + improve a bit Seo setup

* fix bootstrap theme

Co-authored-by: John <john.reilly@investec.co.uk>
Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
John Reilly 2021-08-20 15:36:18 +01:00 committed by GitHub
parent 69ec013248
commit 08597045ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 90 additions and 72 deletions

View file

@ -21,7 +21,7 @@ declare module '@theme/BlogSidebar' {
} }
declare module '@theme/BlogPostPage' { declare module '@theme/BlogPostPage' {
import type {TOCItem} from '@docusaurus/types'; import type {FrontMatterTag} from '@docusaurus/utils';
import type {BlogSidebar} from '@theme/BlogSidebar'; import type {BlogSidebar} from '@theme/BlogSidebar';
export type FrontMatter = { export type FrontMatter = {
@ -29,7 +29,7 @@ declare module '@theme/BlogPostPage' {
readonly title: string; readonly title: string;
readonly author?: string; readonly author?: string;
readonly image?: string; readonly image?: string;
readonly tags?: readonly string[]; readonly tags?: readonly FrontMatterTag[];
readonly keywords?: readonly string[]; readonly keywords?: readonly string[];
readonly author_url?: string; readonly author_url?: string;
readonly authorURL?: string; readonly authorURL?: string;

View file

@ -171,6 +171,7 @@ declare module '@theme/Seo' {
readonly description?: string; readonly description?: string;
readonly keywords?: readonly string[] | string; readonly keywords?: readonly string[] | string;
readonly image?: string; readonly image?: string;
readonly children?: ReactNode;
}; };
const Seo: (props: Props) => JSX.Element; const Seo: (props: Props) => JSX.Element;

View file

@ -34,7 +34,7 @@ function Layout(props: Props): JSX.Element {
const metaImage = image || defaultImage; const metaImage = image || defaultImage;
let metaImageUrl = siteUrl + useBaseUrl(metaImage); let metaImageUrl = siteUrl + useBaseUrl(metaImage);
if (!isInternalUrl(metaImage)) { if (!isInternalUrl(metaImage)) {
metaImageUrl = metaImage; metaImageUrl = metaImage as string;
} }
const faviconUrl = useBaseUrl(favicon); const faviconUrl = useBaseUrl(favicon);

View file

@ -35,7 +35,7 @@ function Layout(props: Props): JSX.Element {
const metaImage = image || defaultImage; const metaImage = image || defaultImage;
let metaImageUrl = siteUrl + useBaseUrl(metaImage); let metaImageUrl = siteUrl + useBaseUrl(metaImage);
if (!isInternalUrl(metaImage)) { if (!isInternalUrl(metaImage)) {
metaImageUrl = metaImage; metaImageUrl = metaImage as string;
} }
const faviconUrl = useBaseUrl(favicon); const faviconUrl = useBaseUrl(favicon);

View file

@ -13,7 +13,6 @@ import Link from '@docusaurus/Link';
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
import {usePluralForm} from '@docusaurus/theme-common'; import {usePluralForm} from '@docusaurus/theme-common';
import MDXComponents from '@theme/MDXComponents'; import MDXComponents from '@theme/MDXComponents';
import Seo from '@theme/Seo';
import EditThisPage from '@theme/EditThisPage'; import EditThisPage from '@theme/EditThisPage';
import type {Props} from '@theme/BlogPostItem'; import type {Props} from '@theme/BlogPostItem';
@ -60,7 +59,7 @@ function BlogPostItem(props: Props): JSX.Element {
title, title,
editUrl, editUrl,
} = metadata; } = metadata;
const {author, keywords} = frontMatter; const {author} = frontMatter;
const image = frontMatterAssets.image ?? frontMatter.image; const image = frontMatterAssets.image ?? frontMatter.image;
@ -129,9 +128,6 @@ function BlogPostItem(props: Props): JSX.Element {
}; };
return ( return (
<>
<Seo {...{keywords, image}} />
<article <article
className={!isBlogPostPage ? 'margin-bottom--xl' : undefined} className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}
itemProp="blogPost" itemProp="blogPost"
@ -140,10 +136,7 @@ function BlogPostItem(props: Props): JSX.Element {
{renderPostHeader()} {renderPostHeader()}
{image && ( {image && (
<meta <meta itemProp="image" content={withBaseUrl(image, {absolute: true})} />
itemProp="image"
content={withBaseUrl(image, {absolute: true})}
/>
)} )}
<div className="markdown" itemProp="articleBody"> <div className="markdown" itemProp="articleBody">
@ -185,7 +178,6 @@ function BlogPostItem(props: Props): JSX.Element {
</footer> </footer>
)} )}
</article> </article>
</>
); );
} }

View file

@ -6,6 +6,7 @@
*/ */
import React from 'react'; import React from 'react';
import Seo from '@theme/Seo';
import BlogLayout from '@theme/BlogLayout'; import BlogLayout from '@theme/BlogLayout';
import BlogPostItem from '@theme/BlogPostItem'; import BlogPostItem from '@theme/BlogPostItem';
import BlogPostPaginator from '@theme/BlogPostPaginator'; import BlogPostPaginator from '@theme/BlogPostPaginator';
@ -15,13 +16,14 @@ import {ThemeClassNames} from '@docusaurus/theme-common';
function BlogPostPage(props: Props): JSX.Element { function BlogPostPage(props: Props): JSX.Element {
const {content: BlogPostContents, sidebar} = props; const {content: BlogPostContents, sidebar} = props;
const {frontMatter, frontMatterAssets, metadata} = BlogPostContents; const {frontMatter, frontMatterAssets, metadata} = BlogPostContents;
const {title, description, nextItem, prevItem} = metadata; const {title, description, nextItem, prevItem, date, tags} = metadata;
const {hide_table_of_contents: hideTableOfContents} = frontMatter; const {hide_table_of_contents: hideTableOfContents, keywords} = frontMatter;
const image = frontMatterAssets.image ?? frontMatter.image;
const authorURL = frontMatter.author_url || frontMatter.authorURL;
return ( return (
<BlogLayout <BlogLayout
title={title}
description={description}
wrapperClassName={ThemeClassNames.wrapper.blogPages} wrapperClassName={ThemeClassNames.wrapper.blogPages}
pageClassName={ThemeClassNames.page.blogPostPage} pageClassName={ThemeClassNames.page.blogPostPage}
sidebar={sidebar} sidebar={sidebar}
@ -30,6 +32,24 @@ function BlogPostPage(props: Props): JSX.Element {
? BlogPostContents.toc ? BlogPostContents.toc
: undefined : undefined
}> }>
<Seo
// TODO refactor needed: it's a bit annoying but Seo MUST be inside BlogLayout
// otherwise default image (set by BlogLayout) would shadow the custom blog post image
title={title}
description={description}
keywords={keywords}
image={image}>
<meta property="og:type" content="article" />
<meta property="article:published_time" content={date} />
{authorURL && <meta property="article:author" content={authorURL} />}
{tags.length > 0 && (
<meta
property="article:tag"
content={tags.map((tag) => tag.label).join(',')}
/>
)}
</Seo>
<BlogPostItem <BlogPostItem
frontMatter={frontMatter} frontMatter={frontMatter}
frontMatterAssets={frontMatterAssets} frontMatterAssets={frontMatterAssets}
@ -37,6 +57,7 @@ function BlogPostPage(props: Props): JSX.Element {
isBlogPostPage> isBlogPostPage>
<BlogPostContents /> <BlogPostContents />
</BlogPostItem> </BlogPostItem>
{(nextItem || prevItem) && ( {(nextItem || prevItem) && (
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} /> <BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
)} )}

View file

@ -104,13 +104,14 @@ export default function LayoutHead(props: Props): JSX.Element {
{favicon && <link rel="shortcut icon" href={faviconUrl} />} {favicon && <link rel="shortcut icon" href={faviconUrl} />}
<title>{pageTitle}</title> <title>{pageTitle}</title>
<meta property="og:title" content={pageTitle} /> <meta property="og:title" content={pageTitle} />
{image ||
(defaultImage && (
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
))}
</Head> </Head>
<Seo {...{description, keywords, image}} /> {/* image can override the default image */}
{defaultImage && <Seo image={defaultImage} />}
{image && <Seo image={image} />}
<Seo description={description} keywords={keywords} />
<CanonicalUrlHeaders /> <CanonicalUrlHeaders />

View file

@ -7,8 +7,8 @@
import React from 'react'; import React from 'react';
import Head from '@docusaurus/Head'; import Head from '@docusaurus/Head';
import {useThemeConfig, useTitleFormatter} from '@docusaurus/theme-common'; import {useTitleFormatter} from '@docusaurus/theme-common';
import useBaseUrl from '@docusaurus/useBaseUrl'; import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
import type {Props} from '@theme/Seo'; import type {Props} from '@theme/Seo';
@ -17,10 +17,11 @@ export default function Seo({
description, description,
keywords, keywords,
image, image,
children,
}: Props): JSX.Element { }: Props): JSX.Element {
const {image: defaultImage} = useThemeConfig();
const pageTitle = useTitleFormatter(title); const pageTitle = useTitleFormatter(title);
const pageImage = useBaseUrl(image || defaultImage, {absolute: true}); const {withBaseUrl} = useBaseUrlUtils();
const pageImage = image ? withBaseUrl(image, {absolute: true}) : undefined;
return ( return (
<Head> <Head>
@ -41,6 +42,8 @@ export default function Seo({
{pageImage && <meta property="og:image" content={pageImage} />} {pageImage && <meta property="og:image" content={pageImage} />}
{pageImage && <meta name="twitter:image" content={pageImage} />} {pageImage && <meta name="twitter:image" content={pageImage} />}
{children}
</Head> </Head>
); );
} }

View file

@ -101,7 +101,7 @@ export type ThemeConfig = {
prism: PrismConfig; prism: PrismConfig;
footer?: Footer; footer?: Footer;
hideableSidebar: boolean; hideableSidebar: boolean;
image: string; image?: string;
metadatas: Array<Record<string, string>>; metadatas: Array<Record<string, string>>;
sidebarCollapsible: boolean; sidebarCollapsible: boolean;
}; };