feat(docs): honor description and keywords frontmatter in docs <head>#834
feat(docs): honor description and keywords frontmatter in docs <head>#834AlemTuzlak wants to merge 1 commit intoTanStack:mainfrom
Conversation
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.
👷 Deploy request for tanstack pending review.Visit the deploys page to approve it
|
📝 WalkthroughWalkthroughThe changes add SEO keyword support to the documentation system by extracting keywords from document frontmatter, processing them through utility functions, exposing them in route loaders, and including them in the head metadata generation for both library docs and framework-specific docs. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/utils/docs.functions.ts (2)
230-232: LGTM on the description/keywords wiring.Preferring
frontMatter.userDescriptionover the markdown-stripped excerpt correctly honors author-provided copy, andextractFrontMatterKeywordsgracefully handles arrays, strings, and missing values.One optional thought: you may want to de-duplicate keywords (case-insensitive) before joining, since authored lists can drift. Not blocking.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/docs.functions.ts` around lines 230 - 232, The keywords can include duplicates with differing case; update extractFrontMatterKeywords (or the place where keywords are joined into the final string) to normalize (e.g., toLowerCase) and deduplicate keywords case-insensitively before returning/joining them so authored lists don’t produce repeated entries; ensure this preserves original trimming and handles arrays/strings/missing values and keep description logic (frontMatter.userDescription ?? removeMarkdown(frontMatter.excerpt ?? '')) unchanged.
267-283: Non-string array entries are silently dropped — consider logging.
extractFrontMatterKeywordsfilters out non-string array items (e.g. numbers or nested arrays in YAML). That's safe, but a silent drop can make misauthored frontmatter hard to debug. Consider aconsole.warnwhenvalueis an array but some entries are filtered out, or when the input type is unexpected (not array/string/undefined).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/docs.functions.ts` around lines 267 - 283, The function extractFrontMatterKeywords currently drops non-string array entries and unknown input types silently; update it to warn when that happens by detecting when value is an array and some items are filtered out (i.e., original length !== normalized.length) and calling console.warn (or the project logger) with a clear message including the offending original value and which entries were dropped, and also emit a warning when value is neither string nor array (and not undefined) to surface unexpected frontmatter types; keep the existing return behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/utils/documents.server.ts`:
- Around line 424-443: userDescription currently checks trimmed length but
returns the original untrimmed result.data.description; update the logic around
userDescription and the returned data.description so any validated description
is trimmed before being stored/returned. Specifically, in the block that
computes userDescription (referencing result.data.description and
userDescription) set userDescription to result.data.description.trim() when
non-empty, and ensure the returned data.description uses this trimmed
userDescription (falling back to createExcerpt(result.content) as before) so no
leading/trailing whitespace leaks into meta tags.
---
Nitpick comments:
In `@src/utils/docs.functions.ts`:
- Around line 230-232: The keywords can include duplicates with differing case;
update extractFrontMatterKeywords (or the place where keywords are joined into
the final string) to normalize (e.g., toLowerCase) and deduplicate keywords
case-insensitively before returning/joining them so authored lists don’t produce
repeated entries; ensure this preserves original trimming and handles
arrays/strings/missing values and keep description logic
(frontMatter.userDescription ?? removeMarkdown(frontMatter.excerpt ?? ''))
unchanged.
- Around line 267-283: The function extractFrontMatterKeywords currently drops
non-string array entries and unknown input types silently; update it to warn
when that happens by detecting when value is an array and some items are
filtered out (i.e., original length !== normalized.length) and calling
console.warn (or the project logger) with a clear message including the
offending original value and which entries were dropped, and also emit a warning
when value is neither string nor array (and not undefined) to surface unexpected
frontmatter types; keep the existing return behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 34e86ee4-4f7f-40cb-a3b1-ef9d76f7ad3a
📒 Files selected for processing (4)
src/routes/$libraryId/$version.docs.$.tsxsrc/routes/$libraryId/$version.docs.framework.$framework.$.tsxsrc/utils/docs.functions.tssrc/utils/documents.server.ts
| const userDescription = | ||
| typeof result.data.description === 'string' && | ||
| result.data.description.trim().length > 0 | ||
| ? result.data.description | ||
| : undefined | ||
|
|
||
| return { | ||
| ...result, | ||
| data: { | ||
| ...result.data, | ||
| description: createExcerpt(result.content), | ||
| description: userDescription ?? createExcerpt(result.content), | ||
| redirect_from: redirectFrom, | ||
| redirectFrom, | ||
| } as { [key: string]: any } & { | ||
| description: string | ||
| redirect_from?: Array<string> | ||
| redirectFrom?: Array<string> | ||
| }, | ||
| userDescription, | ||
| } |
There was a problem hiding this comment.
Consider trimming userDescription before returning.
userDescription is validated via .trim().length > 0 but the original (untrimmed) result.data.description is returned. If authors accidentally include leading/trailing whitespace or newlines (common with YAML block scalars like description: >), that whitespace will leak into the rendered <meta name="description"> and OG/Twitter tags downstream.
Proposed fix
const userDescription =
typeof result.data.description === 'string' &&
result.data.description.trim().length > 0
- ? result.data.description
+ ? result.data.description.trim()
: undefined📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const userDescription = | |
| typeof result.data.description === 'string' && | |
| result.data.description.trim().length > 0 | |
| ? result.data.description | |
| : undefined | |
| return { | |
| ...result, | |
| data: { | |
| ...result.data, | |
| description: createExcerpt(result.content), | |
| description: userDescription ?? createExcerpt(result.content), | |
| redirect_from: redirectFrom, | |
| redirectFrom, | |
| } as { [key: string]: any } & { | |
| description: string | |
| redirect_from?: Array<string> | |
| redirectFrom?: Array<string> | |
| }, | |
| userDescription, | |
| } | |
| const userDescription = | |
| typeof result.data.description === 'string' && | |
| result.data.description.trim().length > 0 | |
| ? result.data.description.trim() | |
| : undefined | |
| return { | |
| ...result, | |
| data: { | |
| ...result.data, | |
| description: userDescription ?? createExcerpt(result.content), | |
| redirect_from: redirectFrom, | |
| redirectFrom, | |
| } as { [key: string]: any } & { | |
| description: string | |
| redirect_from?: Array<string> | |
| redirectFrom?: Array<string> | |
| }, | |
| userDescription, | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/documents.server.ts` around lines 424 - 443, userDescription
currently checks trimmed length but returns the original untrimmed
result.data.description; update the logic around userDescription and the
returned data.description so any validated description is trimmed before being
stored/returned. Specifically, in the block that computes userDescription
(referencing result.data.description and userDescription) set userDescription to
result.data.description.trim() when non-empty, and ensure the returned
data.description uses this trimmed userDescription (falling back to
createExcerpt(result.content) as before) so no leading/trailing whitespace leaks
into meta tags.
Summary
Right now, doc page
<meta name="description">is always auto-generated from the first paragraph of the file — anydescriptionin frontmatter is ignored — and<meta name="keywords">is never emitted for doc pages. This means library authors can't author SEO copy alongside their docs.This PR wires the existing frontmatter fields into the docs renderer:
extractFrontMatterpreserves the user-supplieddescriptionwhen present (exposed asuserDescription) and falls back to the existing auto-generated excerpt otherwise. The return type stays compatible (data.descriptionis still a non-empty string).fetchDocs/fetchDocsPageprefer the user description and expose a normalizedkeywordstop-level field. Array input is joined with", "; strings pass through trimmed; empty/missing values becomeundefined.$libraryId/$version/docs/$and the framework variant) passkeywordsthrough toseo(), which already supports it.Library docs can then author:
Pages that omit these fields keep today's exact behavior.
Motivation: TanStack/ai#464 adds
descriptionandkeywordsto every hand-authored doc in the TanStack AI repo. That PR is a no-op until this one lands; together they give TanStack AI docs proper SEO.Test plan
descriptionin frontmatter and confirm<meta name="description">uses it (not the auto-excerpt)keywordsarray and confirm<meta name="keywords">shows the comma-joined listSummary by CodeRabbit
New Features