Sitemap 站点地图
如果你没接触过 sitemap,这篇从零讲起:sitemap 是什么、为什么要做、在 swift-web 里怎么用(静态页和动态页分别怎么进 sitemap)。读完你就能让自己的站点被搜索引擎完整收录。
一、什么是 sitemap(先扫盲)
sitemap(站点地图)是一个列出你网站所有「想被搜索引擎收录」页面 URL 的文件,通常叫 sitemap.xml,放在站点根目录(https://你的域名/sitemap.xml)。它就像交给 Google / Bing 的一份「页面清单」,告诉爬虫:我这站有哪些页、在哪、什么时候更新过。
它长这样(一段最小的 XML):
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2026-06-23</lastmod>
</url>
<url>
<loc>https://example.com/series/123</loc>
</url>
</urlset>
为什么内容站尤其需要它
搜索引擎主要靠爬虫顺着页面里的链接一层层爬来发现页面。这对内容站有两个痛点:
- 页面太多爬不全:一个影视 / 漫画站可能有上万个详情页,爬虫不一定能从首页的链接全部爬到,没被发现的页 = 拿不到自然搜索流量。
- 新页面发现慢:刚上线的详情页,爬虫可能几天后才顺着链接发现。
sitemap 把「我有哪些页」直接喂给搜索引擎,绕过「靠链接慢慢爬」,让收录更全、更快。对靠搜索流量赚广告费的内容站,这是 SEO 的基础设施,不是可选项。
sitemap 是「帮助发现」,不是「保证收录 / 排名」。搜索引擎仍会自己判断每个页要不要收、排多少。但没进 sitemap 的页,被发现的概率明显更低。
二、swift-web 怎么实现:统一一套 @nuxtjs/sitemap
你什么都不用装、不用配,sitemap 就已经在工作了。 framework 把 @nuxtjs/sitemap 作为 SEO 套件(sitemap + robots + schema-org)的一部分统一安装并配置好:
- 站点域名 —— 配了
NUXT_PUBLIC_SITE_URL就用它;留空则跟随用户实际访问的域名(与useSeo的 canonical 同一套逻辑,详见 SEO 指南)。 - 默认排除私有路径 ——
/login、/mine/**、/admin/**等登录态 / 后台页默认不进 sitemap(这些页本就不该被搜索引擎收录)。 - 站点名兜底 —— sitemap 人类可读视图(浏览器直接打开
/sitemap.xml看到的样式页)的标题取 nuxt-site-config 的站点名,framework 会自动从app.head.title兜底(缺失时该视图会显示 "undefined",仅外观、不影响爬虫读原始 XML)。 - 自动写进 robots.txt —— framework 同时装了
@nuxtjs/robots,会在robots.txt自动加一行Sitemap: https://你的域名/sitemap.xml指向,无需手动(搜索引擎读 robots.txt 时就能发现 sitemap)。
也就是说,swift-web 项目的 sitemap 是单一管线、开箱即用的。
::: tip 不要在 nuxt.config 的 modules 里再列 @nuxtjs/sitemap
framework 已经帮你安装并配好了。如果你在 nuxt.config.ts 的 modules 数组里再裸列一次 '@nuxtjs/sitemap',会重复注册、可能把 framework 配好的默认值(域名锁定 / 私有路径排除)冲掉。也不要自己写定时任务 / 服务端路由生成 sitemap.xml——那会和 framework 的这套打架(两套抢同一个 /sitemap.xml)。统一用 framework 这一套即可。
:::
三、静态路由页:自动收录,零配置
有真实路由文件的页面(app/pages/ 下的 index.vue、about-us.vue、terms.vue 等),framework 的 sitemap 会自动发现并收录,你什么都不用做。
验证很简单 —— 本地起 dev 或部署后,访问 /sitemap.xml:
curl https://你的域名/sitemap.xml
你会看到一个 <urlset>,里面列着你那些静态页的 URL。
想排除某些页
私有 / 后台页 framework 已默认排除。要额外排除自己的某些路径(比如某个临时活动页不想被收录),在 swift.config.json 里加:
{
"framework": {
"seo": {
"sitemap": { "exclude": ["/campaign-temp", "/preview/**"] }
}
}
}
** 是通配(匹配该前缀下所有页)。
四、动态路由页:用 sources 接入
这是唯一需要你动手的部分。
问题:按后端 ID 生成的动态详情页 —— 比如 /series/123、/series/456……(对应 app/pages/series/[id].vue 一个文件)—— framework 自动发现不了。因为它只知道有 [id].vue 这个路由文件,不知道后端到底有哪些 id。
解法:把「有哪些动态 URL」做成一个服务端点(数据源 / source),让 sitemap 模块来拉。三步:
① 写一个返回 URL 列表的端点 server/routes/__sitemap__/urls.ts:
// server/routes/__sitemap__/urls.ts
export default defineSitemapEventHandler(async () => {
const locs: string[] = []
let page = 1
// 分页拉后端的 ID 列表,直到拉空
while (true) {
const ids = await nitroApiFetch<(string | number)[]>('/movie/sitemap', { page: String(page) })
if (!ids?.length) break
locs.push(...ids.map((id) => `/series/${id}`))
page++
}
return locs.map((loc) => ({ loc, changefreq: 'weekly' as const, priority: 0.7 }))
})
defineSitemapEventHandler是 sitemap 模块提供的服务端 helper(自动导入,无需 import);nitroApiFetch是 framework 在「无请求上下文」(如此类构建期 / job 场景)下调后端的工具。/movie/sitemap是后端约定的「返回某板块所有 ID」接口,按你项目的后端契约调整。
⚠️ 端点务必放
server/routes/__sitemap__/(不在/api/下)—— starter 模板自带/api/**的 Nitro proxy +@nuxtjs/robotsdisallow/api/**;放/api/下的端点会被代理到后端、命不中本 handler,模块拉不到 URL。
② 在 nuxt.config.ts 把这个端点配成 sitemap 数据源:
// nuxt.config.ts
export default defineNuxtConfig({
sitemap: {
sources: ['/__sitemap__/urls'],
},
})
③ 完成。 访问 /sitemap.xml,那上万个 /series/<id> 就都在里面了。
starter 模板里已经放了
server/routes/__sitemap__/urls.ts的示例(默认返回空数组)+ nuxt.config 的注释配置。真接入时把示例里的拉取逻辑取消注释、按后端契约改、再取消 nuxt.config 里sitemap.sources的注释即可。
必需数据源失败时停止生成
seo.sitemap.mandatorySources?: string[] 默认未启用。将必须完整枚举的数据源显式列入后,framework 使用官方 sitemap:sources hook,以不携带访客 cookie / Authorization 的本地 GET 读取一次,再交原生 sitemap 管线生成 XML。缺少已配置的源、HTTP 失败、重定向、超时或非法 JSON 会抛安全 503,并设置 Cache-Control: no-store;不发布静态页残缺清单,也不自建 XML 或解析器。源必须返回完整 URL 数组或 { urls: [...] },不能吞分页失败。
// nuxt.config.ts
export default defineNuxtConfig({
swiftConfig: { seo: { sitemap: { mandatorySources: ['/__sitemap__/urls'] } } },
sitemap: { sources: ['/__sitemap__/urls'] },
})
mandatorySources 是同站 JSON 源端点的精确名单,不支持外部 URL、query、hash、目录跳转或 XML 路径。它不负责验证源内部的游标完整性、作品资格或索引状态;这些由数据源实现并验收。
本节 API 为本地新增,待 framework#430 发布。消费方须按实际安装版本进行完整 XML 失败 / 恢复验收;本地通过不等于生产部署或收录。
视频 sitemap:条目带 videos(fw#381)
影视 / 视频站的详情页除了 loc,还可以在同一条目上带 videos,输出 Google 的 视频 sitemap 扩展(<video:video>)。framework 对此零配置:@nuxtjs/sitemap 原生支持,defineSitemapEventHandler 的返回类型已含 videos,直接写就有类型提示。
// server/routes/__sitemap__/urls.ts
export default defineSitemapEventHandler(async () => {
const items = await nitroApiFetch<MovieRow[]>('/movie/sitemap')
return items.map((m) => ({
loc: `/series/${m.id}`,
videos: [
{
title: m.title, // 必填
description: m.summary, // 必填
thumbnail_loc: m.seo.img, // 必填;必须是搜索引擎能直接取的普通图,**不能给 .bnc 加密图**
player_loc: `https://your.site/series/${m.id}`, // 与 content_loc 至少给一个;播放页 URL
duration: m.durationSeconds, // 可选,单位**秒**(JSON-LD 的 VideoObject 是 ISO 8601,这里不是)
publication_date: m.publishedAt, // 可选,ISO 8601
},
],
}))
})
要点:
title/description/thumbnail_loc三项必填,content_loc(视频文件)或player_loc(播放页)至少一个;签名 / 会过期的 m3u8 别当content_loc,给player_loc即可duration单位是秒;expiration_date/rating/view_count/family_friendly/restriction等字段按 Google 规范原名传,见VideoEntry类型- 与
useSeo({ video })的VideoObject是同一份事实的两种输出:sitemap 告诉爬虫「有哪些视频页」,JSON-LD 告诉它「这页的视频是什么」,两边的标题 / 封面 / 时长要一致 - 一条 URL 可带多个视频(多集同页时);不带
videos的条目照旧只出普通<url>
量大了会自动拆分
单个 sitemap 文件有上限(见下方「协议速查」:5 万个 URL / 50MB)。超了 @nuxtjs/sitemap 会自动拆成多个子 sitemap + 一个索引文件(/sitemap_index.xml),你不用操心分片逻辑。你那 ~1 万级别的量级,模块在运行时生成完全够用。
::: tip 不再需要旧式「部署期生成静态 XML」
有些老项目的做法是:部署脚本里调后端 API、把所有 URL 写成静态 .xml 文件,再让服务器 serve 这些静态文件。在 swift-web 里不需要这么做了 —— sitemap.sources 让模块在请求时实时从你的端点取数据、自动分片 + 生成索引,少写一大堆 cron / 分片 / 索引代码。
| 旧式:部署期生成静态 XML | swift-web:sitemap.sources | |
|---|---|---|
| 何时生成 | 部署脚本调后端 → 写死静态文件 | 模块在请求 / 预渲染时调你的端点 |
| 谁管分片 / 索引 | 自己写 | 模块自动 |
| 内容新鲜度 | 要重新部署才更新 | 端点实时反映后端 |
| 代码量 | 多(cron + 分片 + 索引 + serve) | 少(一个端点 + 一行配置) |
(极致想省运行时开销,@nuxtjs/sitemap 也支持预渲染成静态文件,但 ~1 万级别没必要。)
:::
五、协议规范速查(业界标准)
sitemap 是有公开协议的(sitemaps.org 0.9,各大搜索引擎通用)。日常你靠 framework 自动生成、不用手写,但了解这些规则有助于判断「生成对不对」:
| 规则 | 数值 / 说明 |
|---|---|
| 单文件 URL 上限 | 50,000 个 |
| 单文件大小上限 | 50MB(未压缩) |
| 超过上限 | 拆多个子 sitemap + 一个 <sitemapindex> 索引(模块自动) |
| 压缩 | 支持 gzip(.xml.gz),解压后仍须 ≤ 50MB |
<loc> | 页面 URL,必填,≤ 2048 字符 |
<lastmod> | 最后修改时间(W3C 日期格式,如 2026-06-23),Google 会用、但要求真实准确 |
<changefreq> / <priority> | 更新频率 / 优先级,Google 基本忽略,别指望靠它们影响排名 |
关键认知:
priority/changefreq是协议里的「建议字段」,但 Google 现在基本不看;它真正参考的是lastmod(且必须真实,乱填反而失去信任)。所以别在这两个字段上花心思,把lastmod填准更有价值。
六、提交给搜索引擎
生成了 sitemap,还要让搜索引擎知道它的存在。两个途径(swift-web 已帮你做了第一个):
- robots.txt 指向(自动)—— framework 的 robots 集成已在
robots.txt写好Sitemap: https://你的域名/sitemap.xml,搜索引擎抓 robots.txt 时即可发现。访问/robots.txt可确认。 - 搜索引擎站长后台(手动,推荐)—— 在 Google Search Console / Bing Webmaster Tools 里提交 sitemap URL。这是最权威的方式,还能看到「提交了多少 / 收录了多少 / 哪些报错」。上线后建议手动提交一次。
七、上线自检清单
- 访问
/sitemap.xml返回 200 +<urlset>(或多 sitemap 时的/sitemap_index.xml) - 静态页(首页 / about / terms 等)都在里面
- 私有页(login / mine / admin)不在里面
- 动态详情页:若需收录,已配
sitemap.sources+ 端点返回正确 URL - 访问
/robots.txt能看到Sitemap:指向 - 有 prerender 页的项目已配
NUXT_PUBLIC_SITE_URL(构建期没有真实请求,只能用配置值);无 prerender 页时留空是合法的,sitemap 会跟随访问域名 - 上线后在 Google Search Console 手动提交一次
关联阅读
- SEO 最佳实践 —— useSeo / canonical / 结构化数据 / Core Web Vitals
- 渲染模式选用指南 —— sitemap 收录的页该用什么渲染策略
- sitemaps.org 协议 · Google:构建并提交 sitemap · @nuxtjs/sitemap 文档