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/robots disallow /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 / 分片 / 索引代码。

旧式:部署期生成静态 XMLswift-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 已帮你做了第一个):

  1. robots.txt 指向(自动)—— framework 的 robots 集成已在 robots.txt 写好 Sitemap: https://你的域名/sitemap.xml,搜索引擎抓 robots.txt 时即可发现。访问 /robots.txt 可确认。
  2. 搜索引擎站长后台(手动,推荐)—— 在 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 手动提交一次

关联阅读