Skip to content

Sitemap / Canonical / Hreflang 一致性|SEO Metadata

canonical domain、sitemap、hreflang、trailing slash 四者必須完全一致。只要其中一項混用 www / non-www、漏掉語系、或 URL 格式不同,Google 就會把索引訊號拆散,導致收錄異常。

Canonical Domain 統一

只能有一個正式網域

先選定 canonical domain,所有 SEO 輸出都跟著它。以下位置不得混用:

  • site.url
  • runtimeConfig.public.siteUrl
  • NUXT_PUBLIC_SITE_URL 環境變數
  • <link rel="canonical">
  • og:url / Twitter URL
  • JSON-LD url / @id
  • sitemap index 的 <loc>
  • child sitemap 的 <loc>
  • robots.txt 的 Sitemap URL
  • API 回傳的公開分享 URL

Nuxt 設定範例

const canonicalSiteUrl = process.env.NUXT_PUBLIC_SITE_URL || 'https://www.example.com'
export default defineNuxtConfig({
site: {
url: canonicalSiteUrl,
name: '網站名稱',
description: '全站描述',
defaultLocale: 'zh-TW',
trailingSlash: true
},
runtimeConfig: {
public: {
siteUrl: canonicalSiteUrl
}
}
})

常見錯誤:fallback 值用了 non-www。一旦 production 環境漏設環境變數,JSON-LD、canonical、sitemap 全部會指到錯的網域。

// ❌ fallback 是 non-www,production 漏 env 就會裂開
config.public.siteUrl || 'https://example.com'

部署層規則

  • non-www 必須 301/308 到 www(或反向),但只能選一種。
  • Google Search Console 驗證 domain property,或至少同時驗 www / non-www。
  • sitemap 只提交 canonical domain 版本。

Sitemap 品質 SOP

基本原則

  • sitemap 只放「可索引」頁面。
  • sitemap URL 必須是最終 URL,不應回 3xx。
  • sitemap 不能包含 noindex 頁面。
  • sitemap 不能包含登入後頁、付款結果頁、PWA offline fallback、API route。
  • child sitemap 必須在 production 可直接 200 XML

Nuxt 設定範例

sitemap: {
exclude: [
'/console',
'/offline',
'/payment/**',
'/payment/result',
'/account/**',
'/history/**',
'/auth/**',
'/login',
'/register',
'/credits/**',
'/payment-success',
'/checkout/**',
'/api/**'
]
}

Nuxt Content 動態路由

公開內容頁需要主動進 sitemap。生成時應讀取 content collection:

  • route path 以實際公開 URL 為準。
  • lastmod 優先用 updatedAt,再用 date / publishedAt
  • 過濾 draft: trueprivate: truedisabled: true

Sitemap index 與 child sitemap

若使用 i18n 分 child sitemap:

/sitemap_index.xml
/__sitemap__/zh-TW.xml
/__sitemap__/ja-JP.xml
/__sitemap__/en-US.xml

必查項目:

  • sitemap index 是 200 且 Content-Type 是 XML。
  • 每個 child sitemap 都是 200 且 Content-Type 是 XML。
  • sitemap index 的 child <loc> 使用 canonical domain。
  • child sitemap 內 URL 使用 canonical domain。
  • Googlebot UA 不被 Cloudflare challenge。
  • Nitro 能正確 serve server route,不回 SPA HTML。

如果 production 對 child sitemap 支援不穩,短期可退回單一 /sitemap.xml,先恢復 Google 抓取能力,再優化 sitemap index。

多語系 i18n hreflang

路由策略必須先定義

範例策略:

zh-TW: / (預設語系,無 prefix)
ja-JP: /ja
en-US: /en

不要產生不存在的 /zh-TW/ja-JP/en-US 路由。若舊工具可能打到完整 locale code,加 alias redirects:

routeRules: {
'/zh-TW': { redirect: { to: '/', statusCode: 301 } },
'/zh-TW/': { redirect: { to: '/', statusCode: 301 } },
'/ja-JP': { redirect: { to: '/ja', statusCode: 301 } },
'/ja-JP/': { redirect: { to: '/ja', statusCode: 301 } },
'/en-US': { redirect: { to: '/en', statusCode: 301 } },
'/en-US/': { redirect: { to: '/en', statusCode: 301 } }
}

hreflang 範例

<link rel="alternate" hreflang="zh-TW" href="https://www.example.com/cards/major/the-fool/" />
<link rel="alternate" hreflang="ja-JP" href="https://www.example.com/ja/cards/major/the-fool/" />
<link rel="alternate" hreflang="en-US" href="https://www.example.com/en/cards/major/the-fool/" />
<link rel="alternate" hreflang="x-default" href="https://www.example.com/cards/major/the-fool/" />

hreflang 規則

  • HTML head 有 hreflang,不只 sitemap 有 alternate。
  • 每頁包含 self reference。
  • x-default
  • html lang 跟當前語系一致。
  • canonical 指向當前語系自己的 canonical URL。
  • 不混用 zh / zh-TWja / ja-JPen / en-US
  • 只有實際存在或可正常 fallback / redirect 的語系 URL 才產生 alternate。

Trailing Slash 一致性

先選一種策略(有尾斜線或無尾斜線),然後全站一致。

若 production final URL 帶尾斜線:

https://www.example.com/pricing/

則 canonical、sitemap、hreflang、JSON-LD 都要帶尾斜線。不要讓 sitemap URL 回 301/308。

Nuxt Content 注意事項

Nuxt Content 的 path 通常不含尾斜線,但正式 URL 可能含尾斜線。動態頁查 content 時要正規化 slug:

const slugPath = computed(() => {
const s = route.params.slug
return Array.isArray(s)
? s.filter(Boolean).join('/')
: String(s || '').replace(/^\/+|\/+$/g, '')
})
const { data: article } = await useAsyncData(
`article-${slugPath.value.replace(/\//g, '-')}`,
() => queryCollection('content')
.where('path', '=', `/learn/${slugPath.value}`)
.first(),
{ watch: [slugPath] }
)

必測:/learn/history/article-slug/learn/history/article-slug/ 兩者都應 SSR 找到內容,不可一個正常、一個「找不到文章」。

驗證指令

確認不回 301

Trailing slash 檢查

Terminal window
curl -I https://www.example.com/pricing
curl -I https://www.example.com/pricing/

確認不含禁止頁面

Sitemap 品質檢查

Terminal window
# 不應出現 noindex / 私有路由
curl -L https://www.example.com/__sitemap__/zh-TW.xml \
| grep -E "/offline|/payment|/account|/history|/login|/register"
# 應包含公開內容頁
curl -L https://www.example.com/__sitemap__/zh-TW.xml \
| grep "/cards/major/"
# 不應混用 non-www
curl -L https://www.example.com/__sitemap__/zh-TW.xml \
| grep "https://example.com"

完成標準

Checklist

Sitemap / Canonical / Hreflang 一致性

P0

  • site.url / runtimeConfig.public.siteUrl / env 統一使用 canonical domain
  • sitemap index 200 XML
  • child sitemap 200 XML
  • sitemap 不含 noindex / offline / 付款 / 會員 / 登入 / API 頁
  • sitemap URL 不回 3xx / 4xx
  • Nuxt Content 公開頁進 sitemap,draft/private 排除
  • hreflang 有 self reference 和 x-default
  • hreflang URL 與實際路由一致
  • canonical 指向當前語系自己的 URL
  • trailing slash 策略全站一致
  • Nuxt Content 有無尾斜線都能查到內容