# Sitemap到底应该怎么设计？

URL: https://caijiao.org/posts/153
Source: docs/posts/153.md
Description: 单份 5 万条 URL、未压缩 50MB 是 Google 的硬上限，超过才需要 sitemap index 分片。更实用的设计是按类型拆（tools/guides），让 Search Console 分别看索引率；lastmod 必须绑定文件真实修改时间。

先把硬指标摆出来，它决定了后面所有选择：Google 接受的单份 sitemap 最多 50,000 条 URL，未压缩体积不超过 50MB。这两个数是硬限制，超了文件直接不被处理。

但这个阈值在实践中几乎用不上——绝大多数站远没到 5 万页。真正该花心思的是另外三件事：怎么拆、lastmod 怎么生成、哪些 URL 不该放进去。

## 按类型拆，不按数量拆

页面数不到 5 万的时候，拆 sitemap 的理由不是"装不下"，是"看得清"。

我习惯拆成 `sitemap-tools.xml`、`sitemap-guides.xml`、`sitemap-pages.xml`，再用一个 `sitemap.xml` 做 index 指向它们。好处在 Search Console 里立刻能体现：每一份可以单独查看索引率。工具页 92%、指南页 41%，问题范围一秒锁定。

全部塞进一个文件，你只能看到一个总数，然后花一下午手工抽查，还不一定抽到出问题的那批。

分片的另一个好处是重抓节奏可以分开。工具页很少改，一年不动；指南页每周更新。拆开之后只让频繁变动的那份在构建时重新生成，其他几份直接用缓存产物。[Sitemap 与 robots.txt](/google-search-central/04-sitemaps-robots) 里 index 文件的格式就长那样，照抄即可。

## lastmod 绑定真实修改时间，这是最容易做错的一项

`<lastmod>` 的作用只有一个：告诉 Google 这个页面变了，值得再抓一次。

做错的方式也有且只有一种——构建时统一写成 `new Date()`。每次全站构建都把所有 URL 的 lastmod 刷成当天，连续几次之后 Google 会发现这个字段毫无信息量，然后整份忽略。你主动放弃了唯一一个能主动触发重抓的通道，而几万页的站恰恰最依赖这个通道。

正确做法是让它跟文件的真实修改时间绑定。构建脚本里读 Git 提交记录最省事：对每个源文件跑一次 `git log -1 --format=%cI -- <file>`，拿到的 ISO 时间戳直接写进去。

`changefreq` 和 `priority` 两个字段 Google 早就公开说过不采用，输出它们只是增大文件体积，可以直接不写。

## 三类 URL 绝对不能进 sitemap

**被 robots.txt 挡住的。** Google 会报"无法访问"，这类报错积累多了会影响整份文件的可信度。要么放开抓取，要么别列出来，二选一。

**非 canonical 的。** 包括带跟踪参数的地址（`?utm_source=`）、301 跳转前的旧地址、以及 canonical 指向别人的页面。sitemap 是"我希望被索引的页面清单"，不是"我所有 URL 的清单"。

**前端路由产出的、服务端没有对应 HTML 的地址。** 每一个 URL 都必须能直接返回一个完整 HTML 响应。用 [Nginx](/nginx/) 做静态服务时尤其要确认：history 模式的路由有没有配 `try_files` 兜底，别让爬虫拿到 404 或者一个空壳。

## URL 必须绝对地址，且和页面 canonical 一字不差

sitemap 里写相对路径是无效写法，必须带完整协议和域名。

还有个隐蔽的坑：sitemap 里的 URL 和页面 `<link rel="canonical">` 里的 URL 必须完全一致。差一个结尾斜杠，Google 会认为你在声明两个不同的页面，canonical 那一方胜出，sitemap 里的这一条就白列了。大小写、结尾斜杠、`www` 与否这三处要全站统一，最省事的做法是在 Nginx 层把非规范形式 301 掉。

## 几千页以下交给框架，以上自己接管

几千页以内，用框架的路由自动生成就够了。像 10015.io 那种 Next.js 站，50 多个工具页面，sitemap 由框架直接产出，不用自己写。

![10015.io 官网首页截图](/images/2026-09/10015.io.png)

*10015.io 首页放着 Chrome 和 Firefox 两枚扩展入口——工具集的分发不只靠网页。*

页面数上到四位数甚至五位数，建议自己接管：把所有页面数据放进 [SQLite](/sqlite/)，构建时用一条查询 `SELECT slug, updated_at FROM pages WHERE noindex = 0` 直接吐 XML。好处是 lastmod 拿的是数据里的真实字段，构建顺序不影响输出，全量重建一次几分钟可以接受，而且能在同一条 SQL 里顺便过滤掉不该出现的 URL。

生成完之后，在 Search Console 提交一次 index 文件的地址就够了，不用把每份子文件都提交一遍。

---

*Google sitemap 50,000 条 URL / 50MB 上限引自 Search Central 官方文档；10015.io 技术选型（Next.js、50+ 工具）来自其公开说明。*
