修复:Qwik 生成空的 sitemap.xml
Source: Dev.to
问题
在使用 Node.js 适配器(nodeServerAdapter)部署 Qwik(或 Qwik City)应用时,sitemap.xml 在本地可以正常工作,但在生产环境中为空:
<!-- empty sitemap -->
- 本地构建: ✅ 正常
- 开发服务器: ✅ 正确提供 sitemap
- 生产构建: ❌ sitemap 为空,且没有警告
Node.js 适配器在构建期间会运行 静态站点生成(SSG),即使是 SSR 构建。此步骤会:
- 扫描可静态导出的路由
- 根据找到的路由生成 sitemap
- 覆盖你手动放置的
public/sitemap.xml
如果 SSG 步骤找不到任何可导出的路由(例如动态路由、仅 SSR 的路由,或使用 Node.js 适配器时),就会生成一个空的 sitemap。
快速修复 – 禁用 SSG
对于纯 SSR 应用,你可以在适配器配置中彻底关闭 SSG。
adapters/node-server/vite.config.ts
import { nodeServerAdapter } from "@builder.io/qwik-city/adapters/node-server/vite";
import { extendConfig } from "@builder.io/qwik-city/vite";
import baseConfig from "../../vite.config";
export default extendConfig(baseConfig, () => ({
build: {
ssr: true,
rollupOptions: {
input: ["src/entry.express.tsx", "@qwik-city-plan"],
},
},
plugins: [
nodeServerAdapter({
name: "express",
// ⚠️ CRITICAL: Completely disable SSG – use a static sitemap.xml
ssg: null,
}),
],
}));
关键更改: ssg: null 会停止自动 sitemap 生成,这样你放在 public/ 中的文件将保持不变地被提供。
提供静态 Sitemap
在 public/sitemap.xml 中创建你希望被索引的 URL。
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://yourdomain.com/</loc>
<lastmod>2024-12-01</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://yourdomain.com/about/</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
多语言示例
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://yourdomain.com/</loc>
<priority>1.0</priority>
</url>
<url>
<loc>https://yourdomain.com/es/</loc>
<priority>0.8</priority>
</url>
<url>
<loc>https://yourdomain.com/de/</loc>
<priority>0.8</priority>
</url>
</urlset>
调整构建脚本
如果你的 package.json 中运行了 qwik city collect,请将其移除——否则 SSG 会再次被强制执行。
{
"scripts": {
// 之前
// "build:deploy": "npm run build.production && qwik city collect"
// 之后
"build:deploy": "npm run build.production"
}
}
运行构建并验证:
npm run build:deploy
ls -lh dist/sitemap.xml
cat dist/sitemap.xml
你应该看到完整的 sitemap 内容(而不是空文件)。
Node.js 服务器现在会将 dist/sitemap.xml 作为静态资源提供:
node server.js
curl http://localhost:3000/sitemap.xml
动态 Sitemap(SSR 路由)
如果需要在运行时生成 sitemap(例如从 CMS 获取),可以创建一个 SSR 路由。
src/routes/sitemap.xml/index.ts
import type { RequestHandler } from '@builder.io/qwik-city';
const SUPPORTED_LOCALES = ['en-US', 'es', 'ru', 'de', 'fr', 'ja'];
export const onGet: RequestHandler = async ({ url }) => {
const origin = url.origin;
const urls = [
// Root URL
`
${origin}/
${new Date().toISOString().split('T')[0]}
weekly
1.0
`,
// Language‑specific URLs
...SUPPORTED_LOCALES
.filter(l => l !== 'en-US')
.map(l => `
${origin}/${l}/
weekly
0.8
`),
].join('\n');
const sitemap = `
${urls}
`;
return new Response(sitemap, {
status: 200,
headers: {
'Content-Type': 'application/xml; charset=utf-8',
'Cache-Control': 'public, max-age=86400', // 1 day
},
});
};
注意: 在适配器配置中仍然保持
ssg: null,以避免静态生成步骤覆盖此动态路由。
为什么 Node.js 适配器会运行 SSG
- 静态站点生成(SSG) – 在构建时预渲染页面、生成 sitemap 并写入静态文件。
- 服务器端渲染(SSR) – 在运行时按需渲染页面。
Node.js 适配器本意是用于 SSR,但默认仍会触发 SSG,以生成 sitemap 并预渲染任何带有 onStaticGenerate 的页面。当路由是动态的或缺少 onStaticGenerate 时,SSG 找不到可导出的路由,结果写入一个空的 sitemap。
常见陷阱
| 问题 | 产生原因 | 解决办法 |
|---|---|---|
ssg: { exclude: ['*'] } | 排除了文件但仍会运行 SSG,仍会覆盖 sitemap。 | 使用 ssg: null。 |
构建脚本中包含 qwik city collect | 在构建后显式运行 SSG,覆盖你的静态文件。 | 删除该命令。 |
仅依赖 public/ 来放置动态内容 | 动态路由需要 SSR 处理,而不是静态文件。 | 按上文示例实现 SSR 路由。 |
每次构建都会清空 dist/ | 静态文件应放在 public/,不要手动复制到 dist/。 | 保持 public/sitemap.xml。 |
应用修复后的检查清单
- 构建完成后不再出现 “Starting Qwik City SSG…”(当
ssg: null时)。 -
dist/sitemap.xml存在且大小大于空的 109 字节文件。 -
cat dist/sitemap.xml显示预期的 URL 列表。 - 生产服务器返回完整的 sitemap(
curl https://yourdomain.com/sitemap.xml)。 - 构建日志中不再出现与 SSG 相关的警告或错误。
可选:将 /sitemap.xml/ 重定向到 /sitemap.xml
如果请求了带斜杠的路径,可以在服务器入口中添加简单的重定向:
import { createServer } from "http";
createServer((req, res) => {
if (req.url === "/sitemap.xml/") {
res.writeHead(301, { Location: "/sitemap.xml" });
res.end();
return;
}
// ...rest of server logic
}).listen(3000);
该重定向对搜索引擎无害,但可以让 URL 更整洁。
静态 Sitemap 与 SSR 路由的选择
| 使用场景 | 推荐方案 |
|---|---|
| 纯 SSR 应用,路由在开发时已知且 sitemap 很少变化 | 静态 sitemap(public/sitemap.xml + ssg: null) |
| sitemap 数据来自 CMS、数据库或用户生成内容 | SSR 路由(src/routes/sitemap.xml/index.ts) |
| 需要实时更新或按语言区分的逻辑 | SSR 路由 |
| 部署简洁、无需额外构建步骤 | 静态 sitemap |
根据项目需求选择合适的方案,空 sitemap 的问题即可得到解决。