Sitemap到底应该怎么设计?
先把硬指标摆出来,它决定了后面所有选择: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 里 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 做静态服务时尤其要确认: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 首页放着 Chrome 和 Firefox 两枚扩展入口——工具集的分发不只靠网页。
页面数上到四位数甚至五位数,建议自己接管:把所有页面数据放进 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+ 工具)来自其公开说明。