mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-14 09:37:37 +02:00
* 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:
parent
69ec013248
commit
08597045ed
9 changed files with 90 additions and 72 deletions
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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,63 +128,56 @@ function BlogPostItem(props: Props): JSX.Element {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<article
|
||||||
<Seo {...{keywords, image}} />
|
className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}
|
||||||
|
itemProp="blogPost"
|
||||||
|
itemScope
|
||||||
|
itemType="http://schema.org/BlogPosting">
|
||||||
|
{renderPostHeader()}
|
||||||
|
|
||||||
<article
|
{image && (
|
||||||
className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}
|
<meta itemProp="image" content={withBaseUrl(image, {absolute: true})} />
|
||||||
itemProp="blogPost"
|
)}
|
||||||
itemScope
|
|
||||||
itemType="http://schema.org/BlogPosting">
|
|
||||||
{renderPostHeader()}
|
|
||||||
|
|
||||||
{image && (
|
<div className="markdown" itemProp="articleBody">
|
||||||
<meta
|
<MDXProvider components={MDXComponents}>{children}</MDXProvider>
|
||||||
itemProp="image"
|
</div>
|
||||||
content={withBaseUrl(image, {absolute: true})}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="markdown" itemProp="articleBody">
|
{(tags.length > 0 || truncated) && (
|
||||||
<MDXProvider components={MDXComponents}>{children}</MDXProvider>
|
<footer
|
||||||
</div>
|
className={clsx('row docusaurus-mt-lg', {
|
||||||
|
[styles.blogPostDetailsFull]: isBlogPostPage,
|
||||||
|
})}>
|
||||||
|
{tags.length > 0 && (
|
||||||
|
<div className="col">
|
||||||
|
<TagsListInline tags={tags} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{(tags.length > 0 || truncated) && (
|
{isBlogPostPage && editUrl && (
|
||||||
<footer
|
<div className="col margin-top--sm">
|
||||||
className={clsx('row docusaurus-mt-lg', {
|
<EditThisPage editUrl={editUrl} />
|
||||||
[styles.blogPostDetailsFull]: isBlogPostPage,
|
</div>
|
||||||
})}>
|
)}
|
||||||
{tags.length > 0 && (
|
|
||||||
<div className="col">
|
|
||||||
<TagsListInline tags={tags} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isBlogPostPage && editUrl && (
|
{!isBlogPostPage && truncated && (
|
||||||
<div className="col margin-top--sm">
|
<div className="col text--right">
|
||||||
<EditThisPage editUrl={editUrl} />
|
<Link
|
||||||
</div>
|
to={metadata.permalink}
|
||||||
)}
|
aria-label={`Read more about ${title}`}>
|
||||||
|
<b>
|
||||||
{!isBlogPostPage && truncated && (
|
<Translate
|
||||||
<div className="col text--right">
|
id="theme.blog.post.readMore"
|
||||||
<Link
|
description="The label used in blog post item excerpts to link to full blog posts">
|
||||||
to={metadata.permalink}
|
Read More
|
||||||
aria-label={`Read more about ${title}`}>
|
</Translate>
|
||||||
<b>
|
</b>
|
||||||
<Translate
|
</Link>
|
||||||
id="theme.blog.post.readMore"
|
</div>
|
||||||
description="The label used in blog post item excerpts to link to full blog posts">
|
)}
|
||||||
Read More
|
</footer>
|
||||||
</Translate>
|
)}
|
||||||
</b>
|
</article>
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</footer>
|
|
||||||
)}
|
|
||||||
</article>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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 ||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
(defaultImage && (
|
|
||||||
<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 />
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue