feat(docs): honor description and keywords frontmatter in docs <head>#834
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
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe PR exposes document frontmatter keywords and user-provided descriptions through the docs utilities, surfaces keywords in doc loader payloads, and adds those keywords to the head SEO metadata for both generic and framework-specific docs routes. No exported signatures were removed. ChangesDocumentation SEO Pipeline
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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.
…ription-keywords # Conflicts: # src/routes/$libraryId/$version.docs.$.tsx # src/routes/$libraryId/$version.docs.framework.$framework.$.tsx # src/utils/docs.functions.ts
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