事情是这样的。我们项目(一个 Next.js 16 的车商交易平台)上线前要确认 sitemap.xml 是谷歌能识别的格式。我在浏览器里打开 http://localhost:3000/sitemap.xml,页面刷出来一长串:
ruby
https://guineemall.com/ 2026-10-16T05:14:27.224Z daily 1
https://guineemall.com/en/ 2026-10-16T05:14:27.224Z daily 1
https://guineemall.com/search 2026-10-16T05:14:27.224Z daily 0.9
...
贴出来给同事一看,大家都说"这不是纯文本吗?谷歌要的是 <urlset><url><loc> 那种 XML 啊"。
我当时也懵了。因为我的 src/app/sitemap.js 写法明明是返回对象数组:
js
export default async function sitemap() {
return [
{
url: 'https://guineemall.com/',
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
// ...更多
]
}
Next.js 的约定是:sitemap.js 里 export default 一个数组,框架自动帮你序列化成标准 urlset XML。这是官方文档写的,我也不该手写 XML 字符串(Next 16 里手写 return new Response(...) 会直接构建报错,别问我怎么知道的)。
所以问题来了:代码是对的,但浏览器里是纯文本。
我开始了自以为很专业的排查
第一步,绕过浏览器,直接看服务器到底吐了啥:
bash
curl -s http://localhost:3000/sitemap.xml > %TEMP%\sm.xml && notepad %TEMP%\sm.xml
记事本里打开------<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">,标准的 XML。
嗯?服务器是对的。那浏览器为啥是纯文本?
我当时的第一反应是浏览器缓存。毕竟这页面我刷新过好几次了。于是我开始疯狂验证这个假设:
- 让用户在浏览器
Ctrl+Shift+R强刷 → 还是纯文本 - 开无痕窗口 → 还是纯文本
- 清站点缓存 → 还是纯文本
无痕窗口还纯文本,缓存假设当场破产。
然后我走向了更离谱的方向
既然不是缓存,那就是"服务器对浏览器和对 curl 返回了不同的东西"。这个想法一旦冒出来就停不下来了。
我开始怀疑是不是内容协商 :服务器看到 Accept: text/html 或者 Chrome 的 UA,就决定返回纯文本?于是我用 curl 把浏览器的完整请求头都模拟了一遍:
bash
curl -s -i \
-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
-H "Sec-Fetch-Dest: document" \
http://localhost:3000/sitemap.xml
返回还是 content-type: application/xml。
我又想到本机开着 Clash 代理(HTTP_PROXY=http://127.0.0.1:7897),会不会是代理在中间篡改?于是:
bash
# 让 curl 也走代理,看会不会变纯文本
curl -s --proxy "http://127.0.0.1:7897" http://localhost:3000/sitemap.xml
还是 XML。代理也排除了。
接着我把本机所有监听端口撸了一遍:3000、8082(Java 后端)、30985、8050......一个个打过去,没有一个返回那种纯文本。项目里也确认了只有 src/app/sitemap.js 一个生成器,没有第二个 route,没有 middleware 拦截,没有 Service Worker。
到这一步我已经有点魔怔了。最后我使出了"杀进程 + 删 .next + 重启"的大招------因为 Next.js 的 sitemap 这类 metadata 路由在 dev 模式下不会热更新,我猜是旧编译产物赖在缓存里。
powershell
# 结束旧 dev server
taskkill /PID 33632 /F
# 删掉整个 .next(PowerShell 强制删,绕过 bash 的删除钩子)
Remove-Item -Path '.next' -Recurse -Force
重启完再 curl,确实是 XML。我长舒一口气,以为找到了根因,还把"清 .next 就修好了"写进了项目日志。
真相往往很蠢
直到用户自己憋出来一句:
我刚才看错了,页面看起来像纯文本,但其实在开发者工具的 Elements 面板里,它展示的是完整的 XML 结构。
我当场愣住。
浏览器访问 .xml 文件时,本来就会把 XML 当纯文本渲染出来------你看到的就是原始标签,一行一行的,不是 HTML 那种"渲染后的页面"。 这跟"返回了纯文本格式"是两码事。
真正的判断标准是:打开 DevTools → Elements(元素) 面板(不是 Network 的 Response,也不是控制台打印),如果里面是一棵
markdown
urlset
└─ url
├─ loc
├─ lastmod
├─ changefreq
└─ priority
的树,那就说明浏览器收到的 100% 是合法 XML 文档,谷歌爬虫也能正常解析。
我回头一看,我前面所有"死证"------curl 是 XML、notepad 是 XML、走代理是 XML、删 .next 是 XML------其实从一开始就在告诉我同一件事:服务器返回的就是正确的 XML。所谓"浏览器是纯文本",只是 XML 在浏览器里的默认显示方式而已。
而那个"删 .next 修好了",大概率是重启后我让用户重新打开/强刷了一次,他这回终于点开了 Elements 面板看,而不是盯着地址栏下面那坨原始标签。
几个我这次学到的、并想记下来的点
-
浏览器显示 XML 就是"纯文本外观" 。别被页面长得像 txt 骗了。要看它是不是真 XML,用
curl -s -i看Content-Type,或者直接开 DevTools Elements 看 DOM 树。 -
curl不会骗你,但浏览器显示会误导你。 排查这类问题时,curl -s -i http://localhost:3000/sitemap.xml | findstr content-type比在浏览器里瞪眼可靠一百倍。 -
Next.js 的
sitemap.js就老老实实return数组。 别手痒去写 XML 字符串然后return new Response(...),Next 16 会构建失败(response.some is not a function)。框架帮你序列化,这是特性不是限制。 -
dev 模式改了 metadata 路由(sitemap/robots)不一定热更新。 这个怀疑其实不是全错------真遇到"改了代码页面没变",删
.next重启是稳妥的。只是这次它背了锅,真正的问题在别处。 -
先点开 Elements 再看结论。 这次最大的教训就是:我花了一下午验证"服务器没错",却没在最开始就让用户看一眼 DevTools 的 Elements 面板。如果第一步就点开,十分钟就能结束战斗。
写完这篇我自己都觉得好笑。技术排查最怕的不是问题难,是你以为的问题根本不存在。下次再看到浏览器里 XML 长这样,我大概会先笑一下,然后顺手按 F12。