你在 Google Search Console 提交 https://yourdomain.com/sitemap.xml,状态显示 Couldn't fetch 或 404。这是 Astro 站点最常见的首次部署 bug(Next / Hugo 也会因为同样的原因中招)。最可能的单一原因:@astrojs/sitemap 装了但没在 astro.config.mjs 里注册,于是 build 静默地没往 dist/ 里写 sitemap。第二常见:site 没配,integration 直接跳过生成。第三常见:你提交的文件名错了——integration 产出的是 sitemap-index.xml,不是 sitemap.xml。
最快修复: 打开 astro.config.mjs,确认 site: 'https://yourdomain.com' 已设置,且 sitemap()(带括号)出现在 integrations 数组里;跑 npm run build,再 ls dist/sitemap*。如果看到 sitemap-index.xml,就提交这个 URL,而不是 /sitemap.xml。
本文按命中率列出 5 类原因,每个都配一条能直接在 dist/ 或线上 URL 跑的检查命令。
先判断你属于哪一类
| 现象 | 最可能的原因 | 跳转 |
|---|---|---|
dist/ 里完全没有 sitemap* 文件 | 没注册 integration,或 site 没配 | 原因 1、2 |
本地有 dist/sitemap-index.xml,但线上 /sitemap.xml 404 | 提交的文件名错了 | 原因 4 |
线上 URL 返回 200,但 content-type: text/html | 宿主 rewrite / SPA fallback 拦截了 | 原因 3 |
文件跑到了 dist/blog/ 之类的前缀下 | base 路径偏移 | 原因 5 |
文件是合法的(浏览器里能打开成 XML),但 GSC 还是 Couldn't fetch | GSC 抓取延迟或缓存过期 | 见 FAQ |
常见原因
按命中率从高到低排列。
1. 没在 astro.config 注册 @astrojs/sitemap
光 npm install @astrojs/sitemap 不够,必须把它加进 integrations 数组:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://yourdomain.com',
integrations: [sitemap()],
});
少了 integrations: [sitemap()] 这一行,dist/sitemap-index.xml 就不会被生成,而且 build 仍然以 0 退出——不会有任何报错提醒你。
如何判断:
grep -n "sitemap" astro.config.mjs
如果只看到 import 行、integrations 里却没有 sitemap() 调用,就是没注册。
2. site 字段为空
@astrojs/sitemap 用 site 来拼接每个条目的绝对 URL,所以缺了它就拒绝运行。按官方文档,site 是必填项,并且必须以 http:// 或 https:// 开头(截至 2026 年 6 月)。site 缺失时,integration 会在构建时打一条 warning(多数人直接划过去),然后整个跳过生成,dist/ 里什么都不会出现。
如何判断: 跑 npm run build,读 integration 的输出。site 缺失会产生类似 [@astrojs/sitemap] The Sitemap integration requires the site astro.config option 的 warning。看到这行就是它。
3. 宿主 rewrite 把 /sitemap* 当作未匹配路径
跟 RSS 同病——Vercel / Netlify 的 SPA fallback(如 /* -> /index.html)会吞掉 sitemap,返回你的应用外壳。纯静态 Astro 部署一般不会中招,但你如果手动改过 vercel.json 的 rewrites 或 netlify.toml 的 redirects,就要检查。
如何判断:
curl -I "https://yourdomain.com/sitemap.xml"
curl -I "https://yourdomain.com/sitemap-index.xml"
返回 200 但 content-type: text/html(而不是 application/xml)→ SPA fallback 接管了,给你返回的是 HTML。
4. URL 用错——应该是 sitemap-index.xml,不是 sitemap.xml
@astrojs/sitemap 产出的是 sitemap-index.xml 加上一个或多个编号文件(sitemap-0.xml、sitemap-1.xml……)。每超过 45000 个 URL 就会拆出一个新文件(默认 entryLimit,截至 2026 年 6 月)。它不会产出普通的 sitemap.xml。如果你只在 Search Console 提交了 /sitemap.xml,就会拿到 404。
如何判断:
ls dist/sitemap*
如果只看到 sitemap-index.xml 和 sitemap-0.xml、没有普通的 sitemap.xml,那就提交 index 那个 URL。
5. base 路径偏移
如果你的 Astro 配置了 base: '/blog',sitemap 会输出到 dist/blog/sitemap-index.xml,访问 /sitemap.xml(不带 base)就是 404。
如何判断:
grep -n "base:" astro.config.mjs
find dist -name "sitemap*"
实际位置带 base 前缀就是这个原因——提交时要带上前缀(https://yourdomain.com/blog/sitemap-index.xml)。
最短修复路径
按收益从高到低,前 3 步通常解决 80% 的问题。
Step 1:把 @astrojs/sitemap 注册到 config
完整可用的最小配置:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://yourdomain.com', // 必填,且是绝对 http(s) URL
integrations: [
sitemap({
filter: (page) => !page.includes('/admin/'),
changefreq: 'weekly',
priority: 0.7,
}),
],
});
确认两点:
site是http://或https://开头的绝对 URL,惯例上不带末尾斜杠sitemap()出现在integrations里且被实际调用(注意带括号)
Step 2:本地 build 验证产物
rm -rf dist/
npm run build
ls dist/sitemap*
head -20 dist/sitemap-index.xml
期望看到:
dist/sitemap-0.xml
dist/sitemap-index.xml
sitemap-index.xml 内容应该指向 https://yourdomain.com/sitemap-0.xml。如果 site 配错了,这里就会暴露——比如指向 http://localhost:4321/sitemap-0.xml,部署上去 Google 拉不到。先改对 site 再重新 build,然后才部署。
Step 3:部署后用 curl 验证线上
curl -I "https://yourdomain.com/sitemap-index.xml"
curl -s "https://yourdomain.com/sitemap-index.xml" | head -10
通过标准:
- 状态
200 content-type: application/xml- 内容包含
<sitemapindex标签
返回 200 但是 HTML → 有 rewrite 在拦截。去 vercel.json / netlify.toml 检查 SPA fallback,并把 /sitemap* 从中排除。
Step 4:把正确的 URL 提交到 Search Console
在 Search Console 里打开站点资源,左侧导航进 Indexing -> Sitemaps(索引 -> 站点地图)。在 “Add a new sitemap”(添加新的站点地图)处只填路径:
sitemap-index.xml
注意是带 -index 的(除非你通过 entryLimit 或自定义序列化选项拆分过 sitemap)。提交后状态应当在几分钟到几小时内变成 Success;新资源可能要等上一天。如果一直卡在 Couldn't fetch,看 FAQ——那往往是抓取延迟,不是真的失败。
同时把 sitemap 写进 robots.txt,让其他爬虫也能找到:
Sitemap: https://yourdomain.com/sitemap-index.xml
这能帮 Bing、Yandex 等自动发现。另外要确认 robots.txt 没有 Disallow 掉 sitemap 路径——Google 抓 sitemap 时会遵守 robots.txt,一条拦截规则是 Couldn't fetch 的常见成因。
Step 5:CI 部署后冒烟测试
#!/usr/bin/env bash
set -e
URL="https://yourdomain.com/sitemap-index.xml"
status=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
[[ "$status" == "200" ]] || { echo "Sitemap status: $status"; exit 1; }
curl -s "$URL" | grep -q "<sitemapindex" || { echo "Not a sitemap index"; exit 1; }
echo "Sitemap OK"
挂在部署成功 hook 上,任何 commit 不小心删了 sitemap() 调用,都会在 Search Console 看到 404 之前就被立刻发现。
如何确认已经修好
满足下面三条就算搞定:
curl -I https://yourdomain.com/sitemap-index.xml返回200,且content-type: application/xml。- 浏览器里打开这个 URL,显示的是干净的 XML,以
<?xml开头、含<sitemapindex>元素(不是你站点的 HTML 外壳)。 - Search Console 里这条提交显示
Success,且发现的 URL 数大于 0。
如果第 1、2 条都过、Search Console 却还是 Couldn't fetch,那文件本身没问题——是 Google 侧的延迟,见 FAQ。
预防建议
- 加一条 CI 断言:build 后
dist/sitemap-index.xml必须存在;或者部署后跑 Step 5 的冒烟测试 - 在 Search Console 提交后,每周看一次状态,第一时间发现 URL 数下跌
- 在
robots.txt里写明Sitemap:行,让 Bing / Yandex 也能自动发现,并确认没把该路径 disallow 掉 - 别把 sitemap 路径硬编码到模板里,直接信任
@astrojs/sitemap的默认输出 - 改
site字段或base路径时,把 sitemap 测试一并放进同一个 diff 里 review
常见问题(FAQ)
Search Console 显示 Couldn't fetch,但浏览器里 URL 打得开,怎么办?
这通常是 Google 侧的抓取延迟或缓存过期,不是真错误。先确认文件可达:curl -I 应返回 200 和 application/xml。如果是的,就把这条提交留着——Google 往往会在一两天内自己把 Couldn't fetch 翻成 Success。如果卡了好几天,删掉这条 sitemap 重新提交,并对该 sitemap URL 用 URL Inspection(网址检查) 强制重新抓取一次。如果确实是缓存卡死,最后的办法是把 sitemap 部署到一个略有不同的路径,再提交那个新路径。
为什么没有 sitemap.xml,只有 sitemap-index.xml?
这是设计如此。@astrojs/sitemap 始终产出一个索引文件(sitemap-index.xml),指向一个或多个编号子文件(sitemap-0.xml,URL 超过 45000 后会有更多)。提交并链接索引 URL 即可,Google 会自动顺着它去抓子文件。
一定要把 sitemap 提交到 Search Console 吗?
不一定——在 robots.txt 里写 Sitemap: https://yourdomain.com/sitemap-index.xml,爬虫就能自己发现。但在 Search Console 里提交能给你看到发现/已索引的 URL 数,还会暴露解析错误,所以但凡你在意的站点都值得提交。
build 通过了,但 dist/ 里没有 sitemap,为什么不报错?
site 缺失或没注册 sitemap() 都是非致命情况:integration 打一条 warning 后正常退出,于是 npm run build 返回 0。重新 build 一次,读输出里的 [@astrojs/sitemap] 行;或者加上 Step 5 的冒烟测试,让缺产物时 CI 大声报错。
我用的是 SSR / output: 'server',sitemap 还会生成吗?
会,但只覆盖 build 时已知的路由(你预渲染的静态页)。纯动态的 SSR 路由不会自动出现。用 integration 的 customPages 选项把动态 URL 注入进来,或单独为它们生成一份 sitemap。