feat: improve agent readability and discovery#2233
feat: improve agent readability and discovery#2233benjamincanac wants to merge 26 commits intomainfrom
Conversation
Publish well-known agent endpoints (RFC 9727 API catalog, MCP server card), declare AI usage preferences via Content-Signal in robots.txt, and advertise related resources via RFC 8288 Link headers and Vary: Accept, User-Agent on agent-relevant routes. Extend md-rewrite to negotiate markdown via the Accept header and known AI user agents (Vercel only). Add /raw/index.md as the markdown counterpart of the (data-driven) homepage.
Set canonical URLs and a markdown alternate link site-wide via app.vue, plus Organization + WebSite JSON-LD on every page, SoftwareApplication on the homepage, and TechArticle + BreadcrumbList on docs pages. JSON-LD output is HTML-escaped to prevent injection. Replace the meta-refresh stub used by the docs version middleware with a real 301 so agents and crawlers follow it without losing the canonical and structured data.
Publish a markdown sitemap at /sitemap.md so agents can list every page without parsing XML. Add <lastmod> entries to sitemap.xml (post date for blog, today for docs). Add a Nitro plugin that drops the v3 (legacy) and v5 (nightly) sections from /llms.txt to keep it under the agent context window threshold (127 KB → 51 KB); the full content is still served via /llms-full.txt.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Pull all hardcoded host strings through useSiteConfig() / getSiteConfig(event) so canonical, JSON-LD, markdown alternate, sitemap.xml, sitemap.md and the well-known endpoints all use the runtime origin. RFC 8288 Link headers on / become relative URIs so they resolve to the request origin on previews, production and localhost. nuxt-site-config-kit auto-detects VERCEL_URL etc., so the production site.url is provided by the deployment env (and the existing $development override keeps localhost working in dev). Switch /sitemap.md from ISR (with a redundant defineCachedEventHandler) to plain prerender — the underlying collections are static, so a single build- time render is enough. Local audit goes from 73 to 77: JSON-LD, canonical (5/5), and markdown alternate now surface to the auditor since sitemap.xml entries finally point at the audited origin.
- Drop the v5 (nightly) section from /sitemap.md and skip /docs/5.x/* in the Vercel md-rewrite rules so the agent surfaces stay consistent with robots.txt's `Disallow: /docs/5.x/`. - Switch the docs version middleware redirect from 301 to 302 so the target can flip to /docs/5.x/* when Nuxt 5 ships without leaving stale 301s cached forever in browsers and CDNs. - Drop the redundant `transport` field on the MCP server card (only `endpoints[]` is in the current SEP-1649 shape). - Advertise `<rel="alternate"; type="text/html">` alongside the canonical Link on /raw/index.md so agents that landed on the markdown copy can find the rich HTML page. - Remove the hardcoded `softwareVersion: '4'` from the homepage JSON-LD — it would silently go stale when 5 ships and is optional in schema.org/SoftwareApplication. - Use setResponseHeader consistently across the agent-facing routes. - Pin api-catalog and MCP server card service-doc / documentation links to /docs/4.x/guide/ai/mcp so agents skip the docs version redirect hop.
/sitemap.md was listing v3 (legacy) and v5 (nightly) docs while /sitemap.xml only ships v4 + blog. Drop the v3 section so both sitemaps agree on what's surfaced to agents and crawlers; v5 was already removed in the previous commit.
- Hardcode `https://nuxt.com` in /.well-known/api-catalog and /.well-known/mcp/server-card.json so previews advertise the canonical MCP server (matches nuxt/ui pattern). - Wrap getSiteConfig(event).url with withoutTrailingSlash in the deploy- specific routes (sitemap.xml, sitemap.md, raw/index.md) to fix double slashes in generated URLs.
- app/app.vue: suppress canonical on unversioned /docs/* (meta-refresh stubs that the docs-version middleware redirects elsewhere should not advertise themselves as canonical). - app/pages/docs/[...slug].vue: guard prerenderRoutes() with import.meta.server so it doesn't run on every client navigation. - app/middleware/docs-version.global.ts: comment was overstating the benefit — prerendered pages still emit a meta-refresh stub; the 302 only applies to dynamic renders. - server/routes: standardize on defineEventHandler (auto-imported) and drop the manual h3 imports. - modules/md-rewrite.ts: type the nitro arg as Nitro and tighten the negotiated rewrites from (.*) to (.+) so /blog/, /deploy/ trailing- slash variants don't rewrite to /raw/blog/.md.
When Vercel rewrites /docs/foo to /raw/docs/foo.md based on Accept or User-Agent, the cached response at /raw/docs/foo.md must also carry Vary: Accept, User-Agent — otherwise CDNs may serve cached markdown to a browser that asked for HTML, or vice versa. Picked up by the @vercel/agent-readability audit: "Vary: Accept missing — without it CDNs serve cached HTML to agents or markdown to browsers".
`queryCollection` from `@nuxt/content/server` now accepts an H3Event directly — the custom type alias and cast are no longer necessary.
Replace hardcoded version strings and regex patterns across middleware, pages, md-rewrite module, and .well-known endpoints with imports from the single source of truth in `shared/utils/docs.ts`. Remove the redundant DOCS_VERSION re-export from agent-discovery.ts.
Only needed for edge runtimes; the project uses zeroRuntime mode which prerenders OG images at build time on Node via @takumi-rs/core.
defineWebSite now pulls `name` from useSiteConfig() instead of a hardcoded string, keeping it in sync with nuxt.config.ts.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
1 similar comment
✅ Actions performedReview triggered.
|
Rename agent-discovery.ts → site.ts (getSiteUrl) and agent-fingerprint.ts → agent.ts (getAgentFingerprint). Use getSiteUrl in sitemap handlers instead of inline withoutTrailingSlash(getSiteConfig(event).url).
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
shared/utils/docs.ts (1)
13-13: ⚡ Quick winConstrain
CURRENT_DOCS_VERSIONto the supported-version union.Right now it can drift from
SUPPORTED_DOC_VERSIONSwithout a compile-time error.Proposed change
-export const CURRENT_DOCS_VERSION = '4.x' +export const CURRENT_DOCS_VERSION: (typeof SUPPORTED_DOC_VERSIONS)[number] = '4.x'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@shared/utils/docs.ts` at line 13, CURRENT_DOCS_VERSION can drift from SUPPORTED_DOC_VERSIONS causing no compile-time check; change its declaration so its type is the union derived from SUPPORTED_DOC_VERSIONS (e.g. export const SUPPORTED_DOC_VERSIONS = ['x','y'] as const; type SupportedDocVersion = typeof SUPPORTED_DOC_VERSIONS[number]; export const CURRENT_DOCS_VERSION: SupportedDocVersion = '4.x') so the compiler enforces that CURRENT_DOCS_VERSION is one of the supported values referenced by SUPPORTED_DOC_VERSIONS; update the declaration of CURRENT_DOCS_VERSION accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/middleware/docs-version.global.ts`:
- Line 9: The current redirect bypass check in docs-version.global.ts uses
KNOWN_DOC_VERSIONS.some(v => to.path.startsWith(`/docs/${v}`)), which
incorrectly matches prefixes like /docs/4.x-foo; update the condition to enforce
a path boundary by testing either exact match or a slash after the version
(e.g., check KNOWN_DOC_VERSIONS.some(v => to.path === `/docs/${v}` ||
to.path.startsWith(`/docs/${v}/`)) or use a regex ^/docs/${v}($|/)) so only true
version routes pass; modify the check where KNOWN_DOC_VERSIONS and to.path are
referenced to use one of these boundary-safe tests.
In `@server/routes/sitemap.xml.get.ts`:
- Line 35: The sitemap response currently returns streamToPromise(sitemap)
without setting a content type; update the request handler that calls
streamToPromise(sitemap) to explicitly set the response Content-Type to
"application/xml" (e.g., using res.setHeader or response.headers['content-type']
depending on the framework) before returning the stream result so the sitemap
buffer is served with the correct XML MIME type.
---
Nitpick comments:
In `@shared/utils/docs.ts`:
- Line 13: CURRENT_DOCS_VERSION can drift from SUPPORTED_DOC_VERSIONS causing no
compile-time check; change its declaration so its type is the union derived from
SUPPORTED_DOC_VERSIONS (e.g. export const SUPPORTED_DOC_VERSIONS = ['x','y'] as
const; type SupportedDocVersion = typeof SUPPORTED_DOC_VERSIONS[number]; export
const CURRENT_DOCS_VERSION: SupportedDocVersion = '4.x') so the compiler
enforces that CURRENT_DOCS_VERSION is one of the supported values referenced by
SUPPORTED_DOC_VERSIONS; update the declaration of CURRENT_DOCS_VERSION
accordingly.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fee596ee-6057-4816-9bbd-0fc2a60c786c
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
app/app.vueapp/middleware/docs-version.global.tsapp/pages/docs/[...slug].vuemodules/md-rewrite.tsnuxt.config.tspackage.jsonserver/routes/.well-known/api-catalog.get.tsserver/routes/.well-known/mcp/server-card.json.get.tsserver/routes/blog/rss.xml.tsserver/routes/raw/index.md.get.tsserver/routes/sitemap.md.get.tsserver/routes/sitemap.xml.get.tsserver/utils/site.tsshared/utils/docs.ts
✅ Files skipped from review due to trivial changes (3)
- package.json
- server/routes/raw/index.md.get.ts
- nuxt.config.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- server/routes/sitemap.md.get.ts
- server/routes/.well-known/mcp/server-card.json.get.ts
- server/routes/.well-known/api-catalog.get.ts
- modules/md-rewrite.ts
- app/app.vue
- Explicitly set application/xml on sitemap.xml response (was relying on Nitro content-type guessing from the Buffer) - Type CURRENT_DOCS_VERSION as a union of SUPPORTED_DOC_VERSIONS so the compiler rejects values not in the supported list
There was a problem hiding this comment.
🧹 Nitpick comments (1)
nuxt.config.ts (1)
145-165: ⚡ Quick winConsider deduplicating the repeated Vary value.
'Accept, User-Agent'is repeated across several route rules. A shared constant reduces drift risk if this changes later.♻️ Suggested cleanup
+const AGENT_VARY = 'Accept, User-Agent' + export default defineNuxtConfig({ // ... routeRules: { '/': { prerender: true, headers: { Link: [/* ... */].join(', '), - Vary: 'Accept, User-Agent' + Vary: AGENT_VARY } }, - '/modules': { isr: false, prerender: false, headers: { Vary: 'Accept, User-Agent' } }, - '/changelog': { isr: 60 * 60, headers: { Vary: 'Accept, User-Agent' } }, - '/docs/**': { headers: { Vary: 'Accept, User-Agent' } }, - '/blog/**': { headers: { Vary: 'Accept, User-Agent' } }, - '/deploy/**': { headers: { Vary: 'Accept, User-Agent' } }, - '/raw/**': { headers: { Vary: 'Accept, User-Agent' } }, + '/modules': { isr: false, prerender: false, headers: { Vary: AGENT_VARY } }, + '/changelog': { isr: 60 * 60, headers: { Vary: AGENT_VARY } }, + '/docs/**': { headers: { Vary: AGENT_VARY } }, + '/blog/**': { headers: { Vary: AGENT_VARY } }, + '/deploy/**': { headers: { Vary: AGENT_VARY } }, + '/raw/**': { headers: { Vary: AGENT_VARY } }, } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nuxt.config.ts` around lines 145 - 165, Several route entries in nuxt.config.ts repeat the string 'Accept, User-Agent' in headers; define a single constant (e.g., VARY_ACCEPT_USER_AGENT) near the top of the config and replace every headers: { Vary: 'Accept, User-Agent' } occurrence (including the objects in the '/modules', '/changelog', '/docs/**', '/blog/**', '/deploy/**', '/raw/**' route rules) to use that constant instead so the Vary value is centralized and easy to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@nuxt.config.ts`:
- Around line 145-165: Several route entries in nuxt.config.ts repeat the string
'Accept, User-Agent' in headers; define a single constant (e.g.,
VARY_ACCEPT_USER_AGENT) near the top of the config and replace every headers: {
Vary: 'Accept, User-Agent' } occurrence (including the objects in the
'/modules', '/changelog', '/docs/**', '/blog/**', '/deploy/**', '/raw/**' route
rules) to use that constant instead so the Vary value is centralized and easy to
change.
📚 Description
Improves nuxt.com's discoverability and readability for AI agents, following the Vercel agent-readability spec.
/.well-known/api-catalog(RFC 9727),/.well-known/mcp/server-card.json(SEP-1649), RFC 8288Linkheaders,Content-Signalinrobots.txt,Vary: Accept, User-Agenton negotiated routes.Accept: text/markdownand AI user-agent rewrites (Vercel edge), markdown alternate<link>per page viauseCanonicalcomposable,/sitemap.md,/raw/index.md,/raw/modules.md,/raw/changelog.md.nuxt-schema-orgv6 — Organization + WebSite + per-page TechArticle/BreadcrumbList JSON-LD, site-wide canonicals,<lastmod>onsitemap.xml, 302 on docs version redirects.nuxt-og-imagev6 with Takumi renderer,zeroRuntime: true, replacedefineOgImageComponent→defineOgImageacross all pages./raw/**and/.well-known/**usedefineCachedEventHandler(SWR, 1h).shared/utils/docs.ts, dropqueryCollectionWithEventtype cast, drive/raw/index.mdfromcontent/index.yml, trim/llms.txt127 KB → 51 KB.