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 文件