你从 old.com 迁到了 new.com,配好了重定向,也在 Search Console 加了新资源。但打开 https://new.com/sitemap.xml,里面的 URL 还是 <loc>https://old.com/article</loc>。Search Console 把这个 sitemap 标成 “URL not allowed”,并附注 “Check that the URLs all begin with the same domain as your sitemap location.” 重定向明明正常,看着不应该报错——但只要 <loc> 的主机和「提供该 sitemap 的主机」不一致,Google 就会整条丢弃。一个由新域名提供、却全是旧域名 URL 的 sitemap,最终被索引的页面正好是零。
最快的修法: 主机几乎总是在构建时从某一个配置值烤进去的(site、siteUrl 或 baseURL)。把那一个值改成 https://new.com,重新构建、重新部署、清掉 CDN 缓存、再重新提交即可。本文剩下的部分,就是帮你找出到底是哪一个配置值,并确认没有别处还硬编码着旧主机。
Google 为什么拒它(你正撞上的规则)
按 sitemaps.org 协议 和 Google 的 sitemap 文档,sitemap 里的每个 URL 都必须和 sitemap 文件本身在同一个主机上。同一主机指整个 origin 都要一致:scheme(http 还是 https)、www 还是裸域名、注册域名,全都得一模一样。Google 没有跨域名 sitemap 提交功能——你无法名正言顺地在一个托管于 new.com 的 sitemap 里列 old.com 的 URL。所以唯一有效的修法,就是让 sitemap 输出 new.com 的 URL。
先判断你属于哪一类
| sitemap 里的现象 | 最可能的原因 | 去哪看 |
|---|---|---|
每个 <loc> 都是 https://old.com/... | 陈旧的 site URL 构建配置 | site / siteUrl / baseURL |
URL 是 new.com 但 http:// 不是 https://,或 www 与裸域名颠倒 | 同一配置里 scheme/主机写法不对 | 同一个配置值,逐字核对那个字符串 |
sitemap-index.xml 对,但某个子 sitemap 是旧的 | 只有部分构建重新生成,或某个脚本硬编码 | 每个 sitemap-*.xml 文件 |
| 本地构建是对的,线上是旧的 | CDN / 边缘缓存 | 用 curl 加缓存破除参数 |
robots.txt 的 Sitemap: 行指向 old.com | 生成器把旧主机也写进去了 | robots.txt / 生成器配置 |
常见原因
按命中率从高到低排列。
1. Astro site: 配置仍指旧 URL
astro.config.mjs:
export default defineConfig({
site: 'https://old.com', // ← bug
});
@astrojs/sitemap 读根级 site 值来拼绝对 URL,所以 site 陈旧就会产出陈旧 sitemap。有两个 Astro 细节常把人坑住:site 要写在根 defineConfig 里,而不是在 sitemap() 调用内部;而且如果完全没设 site,Astro 会打一条警告并直接跳过 sitemap 生成,不会去猜部署 URL。默认输出文件是 sitemap-index.xml 和 sitemap-0.xml(超过 45,000 个 URL 后再有 sitemap-1.xml 等),所以要按这些确切文件名去查,而不是一个裸的 sitemap.xml。
怎么判断: 打开 astro.config.mjs 读 site 值。是旧的,那就是全部的 bug。
2. Next.js siteUrl 仍解析到旧主机
next-sitemap.config.js:
module.exports = {
siteUrl: process.env.SITE_URL || 'https://old.com',
generateRobotsTxt: true,
};
next-sitemap 要求必须有 siteUrl。如果生产环境没设 SITE_URL,它就 fall back 到硬编码的旧 URL。next-sitemap 是作为 postbuild 脚本跑的,所以一次「成功」的构建照样可能产出旧主机的 sitemap。它还会把 Sitemap: 行(以及可选的 host)写进 robots.txt——这是旧域名藏身的第二处。
怎么判断: 查 .env.production 和你托管平台的环境变量面板里的 SITE_URL,再 cat public/robots.txt(或线上的 robots.txt)确认 Sitemap: 行。
3. Hugo baseURL 没改
config.toml / hugo.toml(或 config/_default/hugo.toml)里:
baseURL = "https://old.com"
Hugo 把 baseURL 烤进每一个生成的绝对 URL,包括 sitemap 和各种 RSS feed。
怎么判断: grep -rn "baseURL" config/ hugo.toml config.toml 2>/dev/null。
4. 自定义 sitemap 脚本硬编码了主机
有些站用自家脚本生成 sitemap,里面拼接了一个硬编码的 host。迁移时改了框架配置,却从没碰过这个脚本。
怎么判断: grep -rn "old.com" scripts/ src/ public/。
5. CDN / 边缘缓存还在发旧 sitemap
你改完配置也重新部署了,但 Cloudflare、Vercel 边缘、Fastly 或 Netlify 仍在发之前缓存的那份 sitemap.xml。
怎么判断: 对比普通请求和加了缓存破除参数的请求。
curl -s "https://new.com/sitemap.xml" | grep -m1 "<loc>"
curl -s "https://new.com/sitemap.xml?nocache=$(date +%s)" | grep -m1 "<loc>"
如果带破除参数的那次返回 new.com、而普通那次返回 old.com,就是缓存。清掉 CDN 缓存(Cloudflare:Caching → Configuration → Purge Everything,或专门清 /sitemap*.xml)后再取一次。
6. Sitemap 分拆了,只有索引重新生成
站点有 sitemap-index.xml 引用 sitemap-0.xml、sitemap-articles.xml、sitemap-pages.xml 等。索引更新了,但某个子 sitemap(往往是由单独步骤或插件生成的)还列着旧 URL。
怎么判断: 打开索引,顺着每个子 <loc> 进去,逐个检查子 sitemap。
最短修复路径
第 1 步:找到 site URL 配置
去你的技术栈拼绝对 URL 的那一个地方找:
- Astro:
astro.config.mjs→ 根级site: - Next.js:
next-sitemap.config.js、next.config.js,以及生产环境的SITE_URL变量 - Hugo:
config.toml/hugo.toml→baseURL - SvelteKit:
svelte.config.js的 adapter 配置,或你的 sitemap 路由读取的那个环境变量 - WordPress(Yoast/RankMath): Settings → General → Site Address (URL),再重新生成 sitemap
- 自定义:
grep -rniE "site_?url|baseurl|canonical" . --exclude-dir=node_modules
第 2 步:改成新的 canonical URL
用确切的 scheme 和主机写法(https://,以及 www 或裸域名——和实际提供 sitemap 的那个保持一致):
// Astro — astro.config.mjs
site: 'https://new.com',
# Hugo — hugo.toml
baseURL = "https://new.com"
# Next.js — .env.production(或平台环境变量面板)
SITE_URL=https://new.com
第 3 步:重新构建,部署前先本地检查
npm run build
grep -h "<loc>" dist/sitemap*.xml | head -20 # Astro 输出到 dist/
每个 <loc> 现在都应是 https://new.com/...。同时检查生成的 robots.txt:
grep -i "sitemap\|host" dist/robots.txt public/robots.txt 2>/dev/null
第 4 步:生产验证
curl -s "https://new.com/sitemap-index.xml" | head -20
curl -s "https://new.com/sitemap-0.xml" | grep -c "old.com" # 期望为 0
如果还出现 old.com,那你看到的是缓存(见原因 5)——清缓存后再取。
第 5 步:提交修正后的 sitemap,并删掉旧条目
在新的 Search Console 资源里:Sitemaps → 填 sitemap.xml(或 sitemap-index.xml,看你实际提供哪个)→ Submit。如果旧资源里还注册着 sitemap,去那里删掉那个条目,免得 Google 继续重新处理旧主机的 URL。
第 6 步:加一道 CI 防线,杜绝回归
# CI 里构建后运行
if grep -rq "old.com" dist/sitemap*.xml dist/robots.txt; then
echo "ERROR: build artifacts still reference old.com"
exit 1
fi
如何确认已修好
curl -s https://new.com/sitemap-index.xml及每个子 sitemap 只返回https://new.com的<loc>值(跑grep -c "old.com"检查 →0)。robots.txt的Sitemap:行指向new.com。- 在 Search Console 里,该 sitemap 状态从 “URL not allowed” 变为 Success,且发现的 URL 数大于 0。重新提交后,这通常需要几小时到几天来重新处理。
容易误判的情况
很多人以为托管平台会「自动检测」部署 URL。多数静态生成器并不会——它们在构建时从你的配置里烤主机。一条从 old.com 到 new.com 的正确重定向同样修不好 sitemap:Google 读的是 <loc> 里字面的主机,在解析 sitemap 时根本不跟随重定向。重定向对用户和链接权重有用,对 sitemap 的主机匹配毫无作用。
预防建议
- 站点 URL 只保留一个数据源(单一配置值或环境变量),sitemap、canonical、OG、RSS 全都从它派生。
- 加上面那条 CI 断言,让陈旧主机直接构建失败,而不是悄悄进了索引。
- 任何域名变更后,先本地构建并检查
sitemap-*.xml和robots.txt,再部署。 - 审每个子 sitemap 和每个自定义脚本,不只是
sitemap-index.xml。 - 部署后总是对 sitemap 做一次缓存破除请求,排除边缘缓存。
FAQ
- Search Console 里确切的报错是什么? “URL not allowed”,描述是 “Check that the URLs all begin with the same domain as your sitemap location.”,在 Sitemaps 报告里按每个 sitemap 显示。
- 我从 old.com 的重定向是好的——Google 为什么还拒这个 sitemap? sitemap 解析读的是
<loc>里字面的主机,不跟随重定向。<loc>的主机必须等于提供该 sitemap 的主机,连 scheme 和www都要一致。 - 能不能在新 sitemap 里提交旧域名 URL,让 Google 迁得更快? 不行。没有跨域名 sitemap 提交。混合主机会被拒;只提供
new.com的 URL。 - Google 多久重新处理修正后的 sitemap? 重新提交后状态通常几小时到一两天内更新;所有页面的完整重新索引则更久。在 Search Console 里重新提交能推一把。
www和非www有区别吗? 有。对 sitemap 解析器来说,https://new.com和https://www.new.com是两个不同的主机。<loc>的主机必须和实际提供该 sitemap 文件的 URL 一致。- 能临时保留两个 sitemap 吗? 能——每语言一个、或每分区一个都行,前提是每个里面的所有 URL 都用 canonical 主机。