From 89461d7989a26a7d6d7034c48ecc77155fe2c112 Mon Sep 17 00:00:00 2001 From: Brian Anglin Date: Mon, 20 Apr 2026 10:33:58 -0700 Subject: [PATCH 1/2] feat(docs): agent-ready Link headers and content signals - Add request middleware that attaches RFC 8288 Link headers to HTML responses (sitemap, llms.txt, service-doc, canonical homepage) so agents can discover related resources. - Declare AI content usage preferences with Content-Signal in robots.txt. Markdown content negotiation (Accept: text/markdown) was already handled by the existing llmMiddleware + fumadocs isMarkdownPreferred pipeline. Co-Authored-By: Claude Opus 4.6 --- src/routes/robots[.]txt.ts | 12 +++++++++++- src/start.ts | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/routes/robots[.]txt.ts b/src/routes/robots[.]txt.ts index c4fcda24..3c96a32a 100644 --- a/src/routes/robots[.]txt.ts +++ b/src/routes/robots[.]txt.ts @@ -4,7 +4,17 @@ import { SITE_URL } from "@/lib/metadata"; export function buildRobotsTxt() { const sitemapUrl = new URL("sitemap.xml", SITE_URL).toString(); - return ["User-agent: *", "Allow: /", `Sitemap: ${sitemapUrl}`, ""].join("\n"); + return [ + // Content Signals declare our preferences for how AI systems and search + // engines may use this content. See https://contentsignals.org/ and + // https://datatracker.ietf.org/doc/draft-romm-aipref-contentsignals/ + "Content-Signal: search=yes, ai-input=yes, ai-train=no", + "", + "User-agent: *", + "Allow: /", + `Sitemap: ${sitemapUrl}`, + "", + ].join("\n"); } export const Route = createFileRoute("/robots.txt")({ diff --git a/src/start.ts b/src/start.ts index 9171d035..12c961be 100644 --- a/src/start.ts +++ b/src/start.ts @@ -132,8 +132,43 @@ const llmMiddleware = createMiddleware().server(({ next, request }) => { return next(); }); +/** + * Links advertised to agents via the `Link` response header (RFC 8288). + * + * Reference: https://www.rfc-editor.org/rfc/rfc8288 + * https://www.iana.org/assignments/link-relations/link-relations.xhtml + */ +const AGENT_LINK_HEADER = [ + '; rel="sitemap"; type="application/xml"', + '; rel="describedby"; type="text/plain"', + '; rel="alternate"; type="text/plain"; title="LLM-friendly full docs"', + '; rel="service-doc"; type="text/html"', + '; rel="canonical"; type="text/html"', +].join(", "); + +function shouldAnnotateResponse(request: Request, response: Response): boolean { + if (request.method !== "GET" && request.method !== "HEAD") return false; + const contentType = response.headers.get("Content-Type"); + if (!contentType) return false; + return contentType.toLowerCase().includes("text/html"); +} + +const agentDiscoveryMiddleware = createMiddleware().server(async ({ next, request }) => { + const result = await next(); + + if (shouldAnnotateResponse(request, result.response)) { + const existing = result.response.headers.get("Link"); + result.response.headers.set( + "Link", + existing ? `${existing}, ${AGENT_LINK_HEADER}` : AGENT_LINK_HEADER, + ); + } + + return result; +}); + export const startInstance = createStart(() => { return { - requestMiddleware: [legacyRedirectMiddleware, llmMiddleware], + requestMiddleware: [agentDiscoveryMiddleware, legacyRedirectMiddleware, llmMiddleware], }; }); From 13444407fefdc3cf7784f2a1f7a8f586b0ed537e Mon Sep 17 00:00:00 2001 From: Brian Anglin Date: Mon, 20 Apr 2026 12:55:28 -0700 Subject: [PATCH 2/2] Update robots[.]txt.ts --- src/routes/robots[.]txt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/robots[.]txt.ts b/src/routes/robots[.]txt.ts index 3c96a32a..09db74dc 100644 --- a/src/routes/robots[.]txt.ts +++ b/src/routes/robots[.]txt.ts @@ -8,7 +8,7 @@ export function buildRobotsTxt() { // Content Signals declare our preferences for how AI systems and search // engines may use this content. See https://contentsignals.org/ and // https://datatracker.ietf.org/doc/draft-romm-aipref-contentsignals/ - "Content-Signal: search=yes, ai-input=yes, ai-train=no", + "Content-Signal: search=yes, ai-input=yes, ai-train=yes", "", "User-agent: *", "Allow: /",