Skip to content

Commit fc2ec40

Browse files
committed
feat(docs): honor description and keywords frontmatter in docs <head>
The docs renderer previously auto-generated a <meta name="description"> from the first paragraph of every doc and ignored any description in frontmatter. It also did not emit a <meta name="keywords"> tag at all. - extractFrontMatter now preserves the user-supplied description when present (exposed as userDescription) and falls back to the existing auto-generated excerpt otherwise. - fetchDocs / fetchDocsPage prefer the user description and expose a normalized keywords field (arrays of strings are joined with ", "; empty values are dropped). - The two docs catch-all routes (\$libraryId/\$version/docs/\$ and the framework variant) pass keywords through to seo(). Individual library docs repos can now author SEO copy in frontmatter: --- title: ... description: "..." keywords: - ... --- Pages that omit these fields keep today's behavior unchanged.
1 parent 0d0fdb0 commit fc2ec40

4 files changed

Lines changed: 32 additions & 2 deletions

File tree

src/routes/$libraryId/$version.docs.$.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const Route = createFileRoute('/$libraryId/$version/docs/$')({
7272
meta: seo({
7373
title: `${loaderData?.title} | ${library.name} Docs`,
7474
description: loaderData?.description,
75+
keywords: loaderData?.keywords,
7576
noindex: library.visible === false,
7677
}),
7778
}

src/routes/$libraryId/$version.docs.framework.$framework.$.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export const Route = createFileRoute(
7575
? `${ctx.loaderData.title} | ${tail}`
7676
: tail,
7777
description: ctx.loaderData?.description,
78+
keywords: ctx.loaderData?.keywords,
7879
noindex: library.visible === false,
7980
}),
8081
}

src/utils/docs.functions.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,9 @@ export const fetchDocs = createServerFn({ method: 'GET' })
227227
}
228228

229229
const frontMatter = extractFrontMatter(file)
230-
const description = removeMarkdown(frontMatter.excerpt ?? '')
230+
const description =
231+
frontMatter.userDescription ?? removeMarkdown(frontMatter.excerpt ?? '')
232+
const keywords = extractFrontMatterKeywords(frontMatter.data.keywords)
231233
const { contentRsc, headings } = await renderMarkdownToRsc(
232234
frontMatter.content,
233235
)
@@ -239,6 +241,7 @@ export const fetchDocs = createServerFn({ method: 'GET' })
239241
contentRsc,
240242
title: frontMatter.data?.title ?? 'Content temporarily unavailable',
241243
description,
244+
keywords,
242245
filePath,
243246
headings,
244247
frontmatter: frontMatter.data,
@@ -253,13 +256,32 @@ export const fetchDocsPage = createServerFn({ method: 'GET' })
253256
return {
254257
contentRsc: doc.contentRsc,
255258
description: doc.description,
259+
keywords: doc.keywords,
256260
filePath: doc.filePath,
257261
frontmatter: doc.frontmatter,
258262
headings: doc.headings,
259263
title: doc.title,
260264
}
261265
})
262266

267+
function extractFrontMatterKeywords(value: unknown): string | undefined {
268+
if (Array.isArray(value)) {
269+
const normalized = value
270+
.filter((item): item is string => typeof item === 'string')
271+
.map((item) => item.trim())
272+
.filter((item) => item.length > 0)
273+
274+
return normalized.length > 0 ? normalized.join(', ') : undefined
275+
}
276+
277+
if (typeof value === 'string') {
278+
const trimmed = value.trim()
279+
return trimmed.length > 0 ? trimmed : undefined
280+
}
281+
282+
return undefined
283+
}
284+
263285
export const fetchFile = createServerFn({ method: 'GET' })
264286
.inputValidator(repoFileInput)
265287
.handler(async ({ data }: { data: RepoFileRequest }) => {

src/utils/documents.server.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,19 +421,25 @@ export function extractFrontMatter(content: string) {
421421
excerpt: (file: any) => (file.excerpt = createRichExcerpt(file.content)),
422422
})
423423
const redirectFrom = normalizeRedirectFrom(result.data.redirect_from)
424+
const userDescription =
425+
typeof result.data.description === 'string' &&
426+
result.data.description.trim().length > 0
427+
? result.data.description
428+
: undefined
424429

425430
return {
426431
...result,
427432
data: {
428433
...result.data,
429-
description: createExcerpt(result.content),
434+
description: userDescription ?? createExcerpt(result.content),
430435
redirect_from: redirectFrom,
431436
redirectFrom,
432437
} as { [key: string]: any } & {
433438
description: string
434439
redirect_from?: Array<string>
435440
redirectFrom?: Array<string>
436441
},
442+
userDescription,
437443
}
438444
}
439445

0 commit comments

Comments
 (0)