Skip to content

Commit 4534ea8

Browse files
authored
Implement unique title tags for sections & variants (#3768)
1 parent 9022d87 commit 4534ea8

File tree

2 files changed

+38
-1
lines changed

2 files changed

+38
-1
lines changed

.changeset/slimy-pens-travel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"gitbook": patch
3+
---
4+
5+
Implement unique title tags for sections & variants

packages/gitbook/src/components/SitePage/SitePage.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,30 @@ export async function generateSitePageViewport(context: GitBookSiteContext): Pro
9292
};
9393
}
9494

95+
/**
96+
* A string concatenation of the site structure (sections and variants) titles.
97+
*/
98+
function getSiteStructureTitle(context: GitBookSiteContext): string | null {
99+
const { sections, siteSpace, siteSpaces } = context;
100+
101+
const title = [];
102+
if (
103+
sections &&
104+
sections.current.default === false && // Only if the current section is not the default one
105+
sections.list.filter((section) => section.object === 'site-section').length > 1 // Only if there are multiple sections
106+
) {
107+
title.push(sections.current.title);
108+
}
109+
if (
110+
siteSpaces.length > 1 && // Only if there are multiple variants
111+
siteSpace.default === false && // Only if the variant is not the default one
112+
siteSpaces.filter((space) => space.space.language === siteSpace.space.language).length > 1 // Only if there are multiple variants *for the current language*. This filters out spaces that are "just" translations of each other, not versions.
113+
) {
114+
title.push(siteSpace.title);
115+
}
116+
return title.join(' ');
117+
}
118+
95119
export async function generateSitePageMetadata(props: SitePageProps): Promise<Metadata> {
96120
const { context, pageTarget } = await getPageDataWithFallback({
97121
context: props.context,
@@ -107,9 +131,17 @@ export async function generateSitePageMetadata(props: SitePageProps): Promise<Me
107131

108132
const { page, ancestors } = pageTarget;
109133
const { site, customization, revision, linker, imageResizer } = context;
134+
const siteStructureTitle = getSiteStructureTitle(context);
110135

111136
return {
112-
title: [page.title, site.title].filter(Boolean).join(' | '),
137+
title: [
138+
page.title,
139+
// Prevent duplicate titles by comparing against the page title.
140+
page.title !== siteStructureTitle ? siteStructureTitle : null, // The first page of a section is often the same as the section title, so we don't need to show it.
141+
page.title !== site.title ? site.title : null, // The site title can also be the same as the site title on the site's landing page.
142+
]
143+
.filter(Boolean)
144+
.join(' | '),
113145
description: page.description ?? '',
114146
alternates: {
115147
// Trim trailing slashes in canonical URL to match the redirect behavior

0 commit comments

Comments
 (0)