关闭

Tryaivo™技术

文档站补齐 v2 三件套,踩到 llms-full 200 陷阱

2026-11-06 04:34:24 浏览:

导语:框架站 DaloyJS 给全部文档页挂上 .md 兄弟文件 + 双通道 link relation,代理不用再猜路径;但缺失的 /llms-full.txt 返回 200 的 HTML 错误页,成了这条链路上最危险的一环。

背景:代理读文档,只能靠猜

DaloyJS 是一个 runtime-portable 的 TypeScript 框架(一套路由跑 Node / Bun / Deno / Cloudflare Workers / Vercel / Fastly / Lambda),文档站点同时承载三样东西:人类阅读的 HTML 页、给机器读的 API 目录(/api/v1、/openapi.json)、以及一个只读 MCP 端点 /mcp。

这个组合的痛点在代理侧很具体:

  • 抓到一个 /docs/routing 之后,代理不知道有没有更适合机器读的版本,只能把整页 HTML 连同导航、样式、脚本一起吞下去;

  • 文档被拆成上百页,代理不知道哪一页覆盖当前问题,也不知道该站的 llms.txt 在哪一级;

  • 猜后缀(.md、.txt、llms.txt)几乎必然错几次,错一次就多一轮 token 消耗。

站点的做法是把「机器可读版本」变成页面级的固定属性,而不是让代理去试探。

落地清单

1. 文件位置:两级 llms.txt + 每页 .md

路径实测体积(2026-11-06)作用
/llms.txt89,945 字节 / 393 行 / 306 个链接 / 23 个 H2 分组全站目录:文档、博客、包、安全与部署条目
/docs/llms.txt52,122 字节 / 318 行 / 239 个链接 / 21 个 H2 分组只覆盖 /docs/ 子树
/docs/*.md单页 1.4 万字节量级(如 /docs/llms-txt.md 为 14,312 字节 / 140 行)每页的 Markdown 正文
/llms-full.txt不存在(见踩坑一)—

这不是「一个大文件塞全站」,而是「根文件做目录、子路径文件做分册、正文按页取」。代理拿到任意一个 URL 之后,往上一级能拿到覆盖它的 llms.txt,平级能拿到该页的 .md,不需要遍历。

2. llms.txt 原文片段

/docs/llms.txt 头部(真实抓取,未改动内容):

# DaloyJS documentation

> Reference documentation for `@daloyjs/core`, a runtime-portable, contract-first
> TypeScript web framework with built-in OpenAPI 3.1 generation, typed client codegen
> (Hey API), and security-first defaults. It runs on Node.js, Bun, Deno, and Cloudflare Workers.

This file covers the pages under `https://daloyjs.dev/docs/`.
For the whole site, including the blog and the published packages, see https://daloyjs.dev/llms.txt.

Every link below points at the markdown version of a docs page. Each markdown file lives at
the page URL with `.md` appended (`/docs/routing` becomes `/docs/routing.md`); drop the suffix
for the canonical HTML. Every docs page also advertises its markdown sibling with a
`rel="alternate" type="text/markdown"` link relation, and points back here with
`rel="describedby"`, on both the HTML `<link>` elements and the HTTP `Link:` response header.

Agents that prefer structured tools over page fetches can query these same docs over the
Model Context Protocol: `https://daloyjs.dev/mcp` is a read-only MCP server with
`search_docs`, `get_doc`, and `list_docs` tools.

## Developer resources

- [DaloyJS API docs](https://daloyjs.dev/docs/api-reference.md): complete public TypeScript surface.
- [DaloyJS OpenAPI spec](https://daloyjs.dev/docs/openapi.md): generate OpenAPI 3.1 from routes.
- [DaloyJS auth docs](https://daloyjs.dev/docs/auth.md): bearer auth and identity providers.
- [DaloyJS webhooks](https://daloyjs.dev/docs/webhook-delivery.md): signed outbound webhook delivery.
- [DaloyJS MCP server](https://daloyjs.dev/mcp): live docs MCP. Guide: https://daloyjs.dev/docs/mcp.md.

## Start here

- [Introduction to DaloyJS](https://daloyjs.dev/docs.md): DaloyJS is a runtime-portable TypeScript
  web framework built around contract-first routing, Standard Schema validation, OpenAPI 3.1
  generation, typed clients, and core security guardrails. Learn what makes it different.
- [Installation](https://daloyjs.dev/docs/installation.md): Install DaloyJS with pnpm, npm, yarn, or bun.

根文件 /llms.txt 的分组(23 个 H2,按顺序):Developer resources、Project、Start here、Tutorials、Migrating、Core concepts、OpenAPI & typed clients、Advanced features、Observability、Security essentials、Attack protection、Hardening & operations、Outbound & webhooks、Data access、Database hosting、Email、Payments、Authentication、Deployment、Adapters & runtimes、Reference、Translations、Optional。

分组按「读者意图」切,不按站点目录树切——这是给代理用的索引,必须能按"我要做什么"跳转,而不是复刻 URL 结构。

3. 双通道 link relation:head 与响应头都发

llms.txt v2 要求页面自己声明「我的 Markdown 版是谁」「覆盖我的 llms.txt 是谁」。这个站点两边都发。

HTML <head> 实测:

<link rel="canonical" href="https://daloyjs.dev/docs/llms-txt"/>
<link rel="alternate" type="text/markdown" href="https://daloyjs.dev/docs/llms-txt.md"/>

HTTP 响应头实测(HEAD https://daloyjs.dev/docs/llms-txt):

Link: </docs/llms-txt.md>; rel="alternate"; type="text/markdown",
      </docs/llms.txt>; rel="describedby",
      </.well-known/api-catalog>; rel="api-catalog",
      </openapi.json>; rel="service-desc"; type="application/vnd.oai.openapi+json;version=3.1"

为什么两边都要:只解析 HTML 的代理看 <link>,只做 HEAD 探测的代理看 Link 头。少一边,就有一半客户端退回猜路径。

4. 内容协商兜底:同一 URL 按 Accept 分流

除了 .md 后缀,还支持用请求头要 Markdown。实测:

curl -sI -H "Accept: text/markdown" https://daloyjs.dev/docs/llms-txt
# 200, Content-Type: text/markdown; charset=utf-8, Vary: Accept, Accept-Encoding, 14,312 字节

14,312 与 /docs/llms-txt.md 的长度完全一致,说明两条路径指向同一份内容,不是两套渲染。

5. robots 配合项

# https://daloyjs.dev/robots.txt
User-Agent: *
Allow: /

Sitemap: https://daloyjs.dev/sitemap.xml

全放行,没有给 GPTBot、ClaudeBot、OAI-SearchBot 单独开组。这里的判断是:既然发 llms.txt 的目的是让代理读,就不要再为爬虫做白名单维护;真正需要防的路径,要么在服务端不提供,要么在 robots 里写清楚,而不是把整个 /docs/ 拦掉却在 llms.txt 里列满 docs 链接——那是最常见的自相矛盾配置。

结果(2026-11-06 实测,非第三方复现)

检查项结果
抽查 5 个 .md 兄弟文件(/docs/routing.md、/docs/auth.md、/docs/mcp.md、/blog.md、/docs/webhook-delivery.md)全部 200,Content-Type: text/markdown; charset=utf-8
Accept: text/markdown 协商200,字节数与 .md 完全一致(14,312)
页面级 rel="alternate" type="text/markdown"存在(HTML <link> 与 HTTP Link 头同时存在)
页面级 rel="describedby"存在(指向 /docs/llms.txt)
根 llms.txt 体积89,945 字节 / 393 行

至于「AI 引用变多、支持工单下降」这类效果数字,本站没有任何对外口径可查,这里不编。能确认的只有机制层面的一件事:代理从一个 URL 出发,可以同时拿到「机器可读正文」和「覆盖它的目录文件」,不用猜后缀、不用遍历目录。

一个值得记录的边界:根 llms.txt 的博客条目仍然指向 HTML 路径(如 https://daloyjs.dev/blog/scaffolding-a-production-ready-daloyjs-app-in-60-seconds),而 /blog.md 实测存在且可用。也就是说「按页给 .md」这件事在 docs 段执行得很彻底,在博客段没有完全对齐——这是实测观察,不是配置错误清单。要抄这套做法,最好一次判定全站哪些路由族必须有 .md,避免同一份 llms.txt 里两种链接形态并存。

复盘

3 条可直接抄的配置项

  1. 根文件只做索引,子路径文件做分册。 根 /llms.txt 声明版本、入口、"全站在哪";/docs/llms.txt 只列该子树。代理先读小文件定位,再取分册,避免一次吞下全站。

  2. 每条链接直接指向最终形态。 写 .../routing.md,不要写 .../routing 让代理自己拼后缀。v2 的链接关系是给机器省推理的,不是给人省字。

  3. 同一关系双通道发布。 HTML <link rel="alternate" type="text/markdown"> 与 HTTP Link: 响应头同时给,rel="describedby" 同时指回 llms.txt;再把 api-catalog、service-desc 这类机器可读入口一并挂上,一个响应里把「正文 / 目录 / API 描述」全交代完。

2 条踩坑提醒

  1. 缺失的 llms-full.txt 会返回 200 的 HTML。 实测 /llms-full.txt 返回 200 + text/html、41,906 字节的 Next.js 错误页;/docs/llms-full.txt 同型号,91,641 字节。代理侧判断"200 就是拿到了",会把一整页 HTML 当 Markdown 读进来。如果站上确实不做 llms-full,路由层要显式返回 404,别让 SPA / 框架的 fallback 兜成 200。

  2. Vary 头 HEAD 与 GET 不一致。 同一个 URL:HEAD 响应的 Vary 只有 rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch,不含 Accept;带 Accept: text/markdown 的 GET 才返回 Vary: Accept, Accept-Encoding。做内容协商的站点如果按 HEAD 结果配置 CDN 缓存键,HTML 与 Markdown 会互相串味。上线后用 GET 复核,不要只看 HEAD。

顺带一条体量判断:89,945 字节的根 llms.txt 按 4 字符/token 粗估约 2.2 万 token,v2 反复强调的"文件本身要小、让代理按需跟随链接"在这个体量上已经不成立。分册是对的,但根文件仍需要继续瘦身——把博客、包、迁移记录这些低频段拆出去,是下一步最省事的一刀。

标注

  • llms.txt 版本:v2(页面自行声明 "Following llms.txt v2",含 rel="alternate" type="text/markdown" + rel="describedby" 双通道)

  • 规范状态:llms.txt 由 Jeremy Howard 于 2024-09-03 提出(v2 修订于 2026-08-10),属作者主张的约定,未被 OpenAI / Anthropic / Google 等 LLM 厂商标记为官方标准;站方是否真去读、读哪段,取决于各客户端实现

  • 来源:https://daloyjs.dev/llms.txt、https://daloyjs.dev/docs/llms.txt、https://daloyjs.dev/docs/llms-txt(页面 + 响应头)、https://daloyjs.dev/robots.txt;规范文本 https://llmstxt.org/

  • 实测时间:2026-11-06

  • 数据口径:全部为本站当日直接抓取(Invoke-WebRequest / curl)所得,非第三方复现;无排名、无引用量数据

标签:llms.txt、AI可见度、AIWO、结构化暴露、开发者工具

标签:https markdown