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

View file

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

View file

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

View file

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

View file

@ -13,7 +13,6 @@ import Link from '@docusaurus/Link';
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
import {usePluralForm} from '@docusaurus/theme-common';
import MDXComponents from '@theme/MDXComponents';
import Seo from '@theme/Seo';
import EditThisPage from '@theme/EditThisPage';
import type {Props} from '@theme/BlogPostItem';
@ -60,7 +59,7 @@ function BlogPostItem(props: Props): JSX.Element {
title,
editUrl,
} = metadata;
const {author, keywords} = frontMatter;
const {author} = frontMatter;
const image = frontMatterAssets.image ?? frontMatter.image;
@ -129,63 +128,56 @@ function BlogPostItem(props: Props): JSX.Element {
};
return (
<>
<Seo {...{keywords, image}} />
<article
className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}
itemProp="blogPost"
itemScope
itemType="http://schema.org/BlogPosting">
{renderPostHeader()}
<article
className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}
itemProp="blogPost"
itemScope
itemType="http://schema.org/BlogPosting">
{renderPostHeader()}
{image && (
<meta itemProp="image" content={withBaseUrl(image, {absolute: true})} />
)}
{image && (
<meta
itemProp="image"
content={withBaseUrl(image, {absolute: true})}
/>
)}
<div className="markdown" itemProp="articleBody">
<MDXProvider components={MDXComponents}>{children}</MDXProvider>
</div>
<div className="markdown" itemProp="articleBody">
<MDXProvider components={MDXComponents}>{children}</MDXProvider>
</div>
{(tags.length > 0 || truncated) && (
<footer
className={clsx('row docusaurus-mt-lg', {
[styles.blogPostDetailsFull]: isBlogPostPage,
})}>
{tags.length > 0 && (
<div className="col">
<TagsListInline tags={tags} />
</div>
)}
{(tags.length > 0 || truncated) && (
<footer
className={clsx('row docusaurus-mt-lg', {
[styles.blogPostDetailsFull]: isBlogPostPage,
})}>
{tags.length > 0 && (
<div className="col">
<TagsListInline tags={tags} />
</div>
)}
{isBlogPostPage && editUrl && (
<div className="col margin-top--sm">
<EditThisPage editUrl={editUrl} />
</div>
)}
{isBlogPostPage && editUrl && (
<div className="col margin-top--sm">
<EditThisPage editUrl={editUrl} />
</div>
)}
{!isBlogPostPage && truncated && (
<div className="col text--right">
<Link
to={metadata.permalink}
aria-label={`Read more about ${title}`}>
<b>
<Translate
id="theme.blog.post.readMore"
description="The label used in blog post item excerpts to link to full blog posts">
Read More
</Translate>
</b>
</Link>
</div>
)}
</footer>
)}
</article>
</>
{!isBlogPostPage && truncated && (
<div className="col text--right">
<Link
to={metadata.permalink}
aria-label={`Read more about ${title}`}>
<b>
<Translate
id="theme.blog.post.readMore"
description="The label used in blog post item excerpts to link to full blog posts">
Read More
</Translate>
</b>
</Link>
</div>
)}
</footer>
)}
</article>
);
}

View file

@ -6,6 +6,7 @@
*/
import React from 'react';
import Seo from '@theme/Seo';
import BlogLayout from '@theme/BlogLayout';
import BlogPostItem from '@theme/BlogPostItem';
import BlogPostPaginator from '@theme/BlogPostPaginator';
@ -15,13 +16,14 @@ import {ThemeClassNames} from '@docusaurus/theme-common';
function BlogPostPage(props: Props): JSX.Element {
const {content: BlogPostContents, sidebar} = props;
const {frontMatter, frontMatterAssets, metadata} = BlogPostContents;
const {title, description, nextItem, prevItem} = metadata;
const {hide_table_of_contents: hideTableOfContents} = frontMatter;
const {title, description, nextItem, prevItem, date, tags} = metadata;
const {hide_table_of_contents: hideTableOfContents, keywords} = frontMatter;
const image = frontMatterAssets.image ?? frontMatter.image;
const authorURL = frontMatter.author_url || frontMatter.authorURL;
return (
<BlogLayout
title={title}
description={description}
wrapperClassName={ThemeClassNames.wrapper.blogPages}
pageClassName={ThemeClassNames.page.blogPostPage}
sidebar={sidebar}
@ -30,6 +32,24 @@ function BlogPostPage(props: Props): JSX.Element {
? BlogPostContents.toc
: 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
frontMatter={frontMatter}
frontMatterAssets={frontMatterAssets}
@ -37,6 +57,7 @@ function BlogPostPage(props: Props): JSX.Element {
isBlogPostPage>
<BlogPostContents />
</BlogPostItem>
{(nextItem || 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} />}
<title>{pageTitle}</title>
<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>
<Seo {...{description, keywords, image}} />
{/* image can override the default image */}
{defaultImage && <Seo image={defaultImage} />}
{image && <Seo image={image} />}
<Seo description={description} keywords={keywords} />
<CanonicalUrlHeaders />

View file

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

View file

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