Skip to content

Commit b064eaa

Browse files
[add] SEO metadata and structured data
- Open Graph and Twitter Card meta tags in site config - default OG image for social sharing - StructuredData component emitting JSON-LD per page type - DocItem Metadata swizzle to inject schema.org data and Twitter tags
1 parent 3322831 commit b064eaa

4 files changed

Lines changed: 141 additions & 0 deletions

File tree

docusaurus.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,14 @@ const config = {
252252
themeConfig:
253253
/* @type {import('@docusaurus/preset-classic').ThemeConfig} */
254254
({
255+
image: 'img/og-default-gantt.png',
256+
metadata: [
257+
{ property: 'og:type', content: 'website' },
258+
{ property: 'og:site_name', content: 'DHTMLX Gantt Docs' },
259+
{ property: 'og:locale', content: 'en_US' },
260+
{ name: 'twitter:card', content: 'summary_large_image' },
261+
{ name: 'twitter:site', content: '@dhtmlx' }
262+
],
255263
navbar: {
256264
title: 'JavaScript Gantt Documentation',
257265
logo: {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React from 'react';
2+
import Head from '@docusaurus/Head';
3+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
4+
import { useBaseUrlUtils } from '@docusaurus/useBaseUrl';
5+
6+
const SITE_NAME = 'DHTMLX Gantt Docs';
7+
const PRODUCT_NAME = 'DHTMLX Gantt';
8+
const PRODUCT_URL = 'https://dhtmlx.com/docs/products/dhtmlxGantt/';
9+
10+
function resolveType(pathname) {
11+
const path = (pathname || '').replace(/\/+$/, '');
12+
if (path === '' || path === '/gantt') return 'SoftwareApplication';
13+
if (/\/api(\/|$)/.test(path)) return 'APIReference';
14+
return 'TechArticle';
15+
}
16+
17+
function buildJsonLd({ type, title, description, url, image, siteUrl }) {
18+
const base = {
19+
'@context': 'https://schema.org',
20+
headline: title,
21+
name: title,
22+
description,
23+
url,
24+
image,
25+
inLanguage: 'en-US',
26+
isPartOf: {
27+
'@type': 'WebSite',
28+
name: SITE_NAME,
29+
url: siteUrl
30+
}
31+
};
32+
33+
if (type === 'SoftwareApplication') {
34+
return {
35+
'@context': 'https://schema.org',
36+
'@type': 'SoftwareApplication',
37+
name: PRODUCT_NAME,
38+
description,
39+
url,
40+
image,
41+
applicationCategory: 'DeveloperApplication',
42+
operatingSystem: 'Cross-platform',
43+
offers: {
44+
'@type': 'Offer',
45+
url: PRODUCT_URL,
46+
priceCurrency: 'USD'
47+
}
48+
};
49+
}
50+
51+
if (type === 'APIReference') {
52+
return {
53+
...base,
54+
'@type': 'APIReference',
55+
programmingModel: 'JavaScript',
56+
about: {
57+
'@type': 'SoftwareApplication',
58+
name: PRODUCT_NAME,
59+
applicationCategory: 'DeveloperApplication'
60+
}
61+
};
62+
}
63+
64+
return {
65+
...base,
66+
'@type': 'TechArticle',
67+
proficiencyLevel: 'Beginner',
68+
dependencies: PRODUCT_NAME,
69+
about: {
70+
'@type': 'SoftwareApplication',
71+
name: PRODUCT_NAME,
72+
applicationCategory: 'DeveloperApplication'
73+
}
74+
};
75+
}
76+
77+
export default function StructuredData({ title, description, pathname, image }) {
78+
const { siteConfig } = useDocusaurusContext();
79+
const { withBaseUrl } = useBaseUrlUtils();
80+
81+
const siteUrl = siteConfig.url + siteConfig.baseUrl.replace(/\/$/, '');
82+
const url = siteConfig.url + withBaseUrl(pathname || '/');
83+
const absoluteImage = image ? withBaseUrl(image, { absolute: true }) : undefined;
84+
85+
const type = resolveType(pathname);
86+
const jsonLd = buildJsonLd({
87+
type,
88+
title,
89+
description,
90+
url,
91+
image: absoluteImage,
92+
siteUrl
93+
});
94+
95+
return (
96+
<Head>
97+
<script type="application/ld+json">
98+
{JSON.stringify(jsonLd)}
99+
</script>
100+
</Head>
101+
);
102+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import Head from '@docusaurus/Head';
3+
import { useLocation } from '@docusaurus/router';
4+
import DocItemMetadata from '@theme-original/DocItem/Metadata';
5+
import { useDoc } from '@docusaurus/plugin-content-docs/client';
6+
import StructuredData from '@site/src/components/StructuredData';
7+
8+
export default function DocItemMetadataWrapper(props) {
9+
const { metadata, frontMatter, assets } = useDoc();
10+
const { pathname } = useLocation();
11+
12+
const title = metadata.title;
13+
const description = metadata.description;
14+
const image = assets.image ?? frontMatter.image;
15+
16+
return (
17+
<>
18+
<DocItemMetadata {...props} />
19+
<Head>
20+
{title && <meta name="twitter:title" content={title} />}
21+
{description && <meta name="twitter:description" content={description} />}
22+
</Head>
23+
<StructuredData
24+
title={title}
25+
description={description}
26+
pathname={pathname}
27+
image={image}
28+
/>
29+
</>
30+
);
31+
}

static/img/og-default-gantt.png

400 KB
Loading

0 commit comments

Comments
 (0)