在 SEO 优化中,sitemap.xml 是搜索引擎快速抓取网站结构的关键文件。对于前后端分离的项目,如果前端是 Nuxt,后端是 Node.js,我们如何优雅地生成 sitemap.xml 呢?本文将带你完整梳理。
1 为什么 Nuxt 前端不适合生成 sitemap.xml
很多开发者会尝试在 Nuxt 前端生成 sitemap.xml,但会遇到几个问题:
-
Nuxt SPA 路由会拦截请求
- 当你访问
/sitemap.xml时,Nuxt 会尝试匹配前端路由,没有对应页面就报 404。
- 当你访问
-
搜索引擎抓取 SPA 时无法执行 JS
- 前端生成的 sitemap.xml 只有在浏览器运行时才存在,而搜索引擎不会执行前端 JS。
-
前端无法直接访问数据库
- sitemap.xml 需要动态获取文章、通知等 URL,前端无法安全地直接访问数据库。
√ 结论:sitemap.xml 应该由后端生成。
2 Node.js 后端生成 sitemap.xml
假设你的后端是 Express + MySQL(或其他数据库),可以按以下步骤生成:
A. 准备数据
假设你有两类数据:文章和通知:
const article = await ExecuteFunc(
'SELECT article_id AS id, pub_date FROM ev_articles WHERE state = 0 AND is_delete = 0'
)
const notify = await ExecuteFunc(
'SELECT notify_id AS id, pub_date FROM ev_notify WHERE whosee = 0 AND state = 0 AND is_delete = 0'
)
我们可以给每条记录加一个 type 字段,区分 URL 前缀:
const articlesWithType = article.map(a => ({ ...a, type: 'article' }))
const notifyWithType = notify.map(n => ({ ...n, type: 'notify' }))
const data = [...articlesWithType, ...notifyWithType]
B. 拼接 XML 字符串
const hostname = 'https://a.com'
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${data.map(a => `
<url>
<loc>${hostname}/${a.type}/${a.id}</loc>
<lastmod>${a.pub_date}</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>`).join('')}
</urlset>`
说明:
<loc>指定页面 URL<lastmod>页面最后更新时间<changefreq>页面更新频率<priority>页面重要性(0.0 ~ 1.0,搜索引擎参考值)
C. 返回 XML 给前端或搜索引擎
app.get('/sitemap.xml', (req, res) => {
res.header('Content-Type', 'application/xml')
res.send(xml)
})
这样,当搜索引擎访问 https://a.com/sitemap.xml 时,就能直接拿到动态生成的 XML。
3 解决 Nuxt SPA 拦截问题
由于前端 Nuxt SPA 会拦截请求,我们可以通过代理或服务器路由避免:
Nginx 代理示例:
server {
listen 80;
server_name a.com;
location / {
proxy_pass http://127.0.0.1:777/; # Nuxt 前端
}
location /api/ {
proxy_pass http://127.0.0.1:666/api/; # Node.js API
}
location /sitemap.xml {
proxy_pass http://127.0.0.1:666/sitemap.xml; # sitemap.xml 由后端提供
}
}
√ 这样:
- 搜索引擎直接访问
/sitemap.xml→ 拿到 XML - Nuxt 前端不会拦截
- 前端完全不接触数据库
4 扁平化静态页面
除了动态文章和通知,你可能还有静态页面,例如:
const staticPages = [
{ path: '/', pub_date: '2025-09-19' },
{ path: '/Login', pub_date: '2025-09-19' },
{ path: '/register', pub_date: '2025-09-19' }
]
同样可以合并到 sitemap:
const allPages = [...staticPages, ...data.map(d => ({
path: `/${d.type}/${d.id}`,
pub_date: d.pub_date
}))]
然后生成 XML 即可。
5 总结
- sitemap.xml 必须由后端生成,前端 SPA 不适合生成
- 动态文章 / 通知 + 静态页面 可以统一生成
- 设置 Content-Type 为 application/xml
- 通过代理或服务器路由解决 Nuxt SPA 拦截问题
这样不仅搜索引擎可以抓取,还能保证前端安全和项目规范。
完整express.js代码
// 做站点地图
exports.sitemapData = async (req, res) => {
const article = await ExecuteFunc(
'SELECT article_id AS id, pub_date FROM ev_articles WHERE state = 0 AND is_delete = 0')
const notify = await ExecuteFunc(
'SELECT notify_id AS id, pub_date FROM ev_notify WHERE whosee = 0 AND state = 0 AND is_delete = 0'
)
const rawData = [
{
pages: [
{ path: '/' },
{ path: '/Login' },
{ path: '/register' },
{ path: '/checkVer' },
{ path: '/DevProcess' },
{ path: '/SpsList' },
]
},
{
pages: [
{ path: '/Notify' },
{ path: '/Search' },
]
},
{
pages: [
{ path: '/space/jihua' },
{ path: '/feedback/class/spslist' },
{ path: '/feedback/sitemap' },
]
},
{
pages: [
{ path: '/error/type-window' },
{ path: '/error/type-phone' },
{ path: '/error/test' },
]
},
]
// 获取今天日期,格式 YYYY-MM-DD
const today = new Date().toISOString().split('T')[0]
// 扁平化
const sitemapArray = rawData.flatMap(category =>
category.pages.map(page => ({
path: page.path,
pub_date: today
}))
)
const hostname = 'http://j-h.top'
// 拼接 XML 字符串
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
// 这是基本page,需要单独提取
${sitemapArray
.map(
(p) => `
<url>
<loc>${hostname}${p.path}</loc>
<lastmod>${p.pub_date}</lastmod>
<changefreq>daily</changefreq>
<priority>1</priority>
</url>`
)
.join('')}
// 假如说你的路由有/article/**那就...
${article
.map(
(a) => `
<url>
<loc>${hostname}/article/${a.id}</loc>
<lastmod>${a.pub_date}</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>`
)
.join('')}
// 假如说你的路由有/notify/**那就...
${notify
.map(
(b) => `
<url>
<loc>${hostname}/notify/${b.id}</loc>
<lastmod>${b.pub_date}</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>`
)
.join('')}
</urlset>`
// 设置返回类型为 XML
res.header('Content-Type', 'application/xml')
res.send(xml)
}
-end 本文由chatgpt梳理汇编文章
编辑:JiHua