Skip to content

Commit bc58b2d

Browse files
feat(docs): add JSON-LD structured data for rich snippets (#7484)
* feat(docs): added JSON-LD structured data for SEO * chore(docs): apply coderabbit comment * fix(docs): build error fixed * chore(docs): coderabbit comment resolved
1 parent acf7f58 commit bc58b2d

File tree

3 files changed

+119
-25
lines changed

3 files changed

+119
-25
lines changed

apps/docs/src/app/(docs)/(default)/[[...slug]]/page.tsx

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
EditOnGitHub,
1313
PageLastUpdate,
1414
} from '@/components/layout/notebook/page';
15+
import { TechArticleSchema, BreadcrumbSchema } from '@/components/structured-data';
1516

1617
interface PageParams {
1718
slug?: string[];
@@ -29,25 +30,28 @@ export default async function Page({
2930
const MDX = page.data.body;
3031

3132
return (
32-
<DocsPage
33-
tableOfContent={{
34-
style: 'normal',
35-
}}
36-
toc={page.data.toc}
37-
full={page.data.full}
38-
>
39-
<div className="flex flex-col md:flex-row items-start gap-4 pt-2 pb-1 md:justify-between">
40-
<DocsTitle>{page.data.title}</DocsTitle>
41-
<div className="flex flex-row gap-2 items-center">
42-
{!page.url.startsWith('/management-api/endpoints') && (
43-
<LLMCopyButton pageUrl={page.url} />
44-
)}
45-
<ViewOptions
46-
pageUrl={page.url}
47-
githubUrl={`https://github.com/prisma/docs/blob/main/apps/docs/content/docs/${page.path}`}
48-
/>
33+
<>
34+
<TechArticleSchema page={page} />
35+
<BreadcrumbSchema page={page} />
36+
<DocsPage
37+
tableOfContent={{
38+
style: 'normal',
39+
}}
40+
toc={page.data.toc}
41+
full={page.data.full}
42+
>
43+
<div className="flex flex-col md:flex-row items-start gap-4 pt-2 pb-1 md:justify-between">
44+
<DocsTitle>{page.data.title}</DocsTitle>
45+
<div className="flex flex-row gap-2 items-center">
46+
{!page.url.startsWith('/management-api/endpoints') && (
47+
<LLMCopyButton pageUrl={page.url} />
48+
)}
49+
<ViewOptions
50+
pageUrl={page.url}
51+
githubUrl={`https://github.com/prisma/docs/blob/main/apps/docs/content/docs/${page.path}`}
52+
/>
53+
</div>
4954
</div>
50-
</div>
5155
<DocsDescription>{page.data.description}</DocsDescription>
5256
<DocsBody>
5357
<MDX
@@ -67,6 +71,7 @@ export default async function Page({
6771
)}
6872
</div>
6973
</DocsPage>
74+
</>
7075
);
7176
}
7277

apps/docs/src/app/(docs)/v6/[[...slug]]/page.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
EditOnGitHub,
1919
PageLastUpdate,
2020
} from '@/components/layout/notebook/page';
21+
import { TechArticleSchema, BreadcrumbSchema } from '@/components/structured-data';
2122

2223
interface PageParams {
2324
version: string;
@@ -37,13 +38,16 @@ export default async function Page({
3738
const MDX = page.data.body;
3839

3940
return (
40-
<DocsPage
41-
tableOfContent={{ style: 'normal' }}
42-
toc={page.data.toc}
43-
full={page.data.full}
44-
>
45-
<div className="flex flex-row items-center gap-4 pt-2 pb-6 justify-between">
46-
<DocsTitle>{page.data.title}</DocsTitle>
41+
<>
42+
<TechArticleSchema page={page} />
43+
<BreadcrumbSchema page={page} />
44+
<DocsPage
45+
tableOfContent={{ style: 'normal' }}
46+
toc={page.data.toc}
47+
full={page.data.full}
48+
>
49+
<div className="flex flex-row items-center gap-4 pt-2 pb-6 justify-between">
50+
<DocsTitle>{page.data.title}</DocsTitle>
4751
<div className="flex flex-row gap-2 items-center">
4852
<LLMCopyButton pageUrl={page.url} />
4953
<ViewOptions
@@ -74,6 +78,7 @@ export default async function Page({
7478
)}
7579
</div>
7680
</DocsPage>
81+
</>
7782
);
7883
}
7984

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { getBaseUrl } from '@/lib/urls';
2+
import type { InferPageType } from 'fumadocs-core/source';
3+
import type { source, sourceV6 } from '@/lib/source';
4+
5+
interface StructuredDataProps {
6+
page: InferPageType<typeof source> | InferPageType<typeof sourceV6>;
7+
}
8+
9+
export function TechArticleSchema({ page }: StructuredDataProps) {
10+
const baseUrl = getBaseUrl();
11+
const lastModified = (page.data as { lastModified?: Date }).lastModified;
12+
13+
const schema = {
14+
'@context': 'https://schema.org',
15+
'@type': 'TechArticle',
16+
headline: (page.data as any).metaTitle ?? page.data.title,
17+
description: (page.data as any).metaDescription ?? page.data.description,
18+
url: `${baseUrl}${page.url}`,
19+
dateModified: lastModified?.toISOString(),
20+
author: {
21+
'@type': 'Organization',
22+
name: 'Prisma Data, Inc.',
23+
url: 'https://www.prisma.io',
24+
},
25+
publisher: {
26+
'@type': 'Organization',
27+
name: 'Prisma',
28+
url: 'https://www.prisma.io',
29+
logo: {
30+
'@type': 'ImageObject',
31+
url: `${baseUrl}/logo.png`,
32+
},
33+
},
34+
mainEntityOfPage: {
35+
'@type': 'WebPage',
36+
'@id': `${baseUrl}${page.url}`,
37+
},
38+
};
39+
40+
return (
41+
<script
42+
type="application/ld+json"
43+
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema).replace(/</g, '\\u003c') }}
44+
/>
45+
);
46+
}
47+
48+
export function BreadcrumbSchema({ page }: StructuredDataProps) {
49+
const baseUrl = getBaseUrl();
50+
51+
// Build breadcrumb items from URL slugs
52+
const breadcrumbItems = [
53+
{
54+
'@type': 'ListItem',
55+
position: 1,
56+
name: 'Home',
57+
item: baseUrl,
58+
},
59+
];
60+
61+
let currentPath = '';
62+
page.slugs.forEach((slug, index) => {
63+
currentPath += `/${slug}`;
64+
breadcrumbItems.push({
65+
'@type': 'ListItem',
66+
position: index + 2,
67+
name: slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, ' '),
68+
item: `${baseUrl}${currentPath}`,
69+
});
70+
});
71+
72+
const schema = {
73+
'@context': 'https://schema.org',
74+
'@type': 'BreadcrumbList',
75+
itemListElement: breadcrumbItems,
76+
};
77+
78+
return (
79+
<script
80+
type="application/ld+json"
81+
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema).replace(/</g, '\\u003c') }}
82+
/>
83+
);
84+
}

0 commit comments

Comments
 (0)