• #SEO
  • #Nuxt
  • #Google Search Console
  • #Cloudflare

トレイリングスラッシュによるURL重複インデックス問題

症状

Google Search Consoleで、同一ページが2つのURLとしてインデックス登録されている:

  • https://log.eurekapu.com/2025-12-04/self-reminder
  • https://log.eurekapu.com/2025-12-04/self-reminder/

原因

デフォルトでトレイリングスラッシュの正規化を行わないNuxt 3では:

  1. 両方のURLでアクセス可能: スラッシュあり・なし両方のURLが有効なルートとして扱われる
  2. リダイレクトなし: どちらかに正規化(canonical化)するリダイレクトが発生しない
  3. Googleが両方をインデックス: 同一コンテンツを別URLとして認識し、重複インデックス

これはSEO的に以下の問題を引き起こす:

  • PageRankの分散: 被リンクが2つのURLに分散される
  • クロールバジェットの浪費: Googleが同じページを2回クロールする
  • 重複コンテンツ警告: 将来的にSearch Consoleで警告が出る可能性

試した解決策(すべて失敗)

方法1: routeRulesでリダイレクト設定 → ビルドエラー

nuxt.config.tsrouteRulesでスラッシュ付きURLをリダイレクト:

export default defineNuxtConfig({
  routeRules: {
    '/**/**/': {
      redirect: {
        to: path => path.slice(0, -1),
        statusCode: 301
      }
    }
  }
})

失敗理由: cloudflare-pages-staticプリセットでは、redirect.toに関数を使用できない。

[error] segment.replace is not a function
  at joinURL ([email protected]/node_modules/ufo/dist/index.mjs:316:32)
  at writeCFPagesRedirects (nitropack/.../cloudflare/utils.mjs:127:105)

方法2: サーバーミドルウェア → 静的ビルドでは動作しない

server/middleware/trailing-slash.tsを作成しても、cloudflare-pages-staticプリセットではサーバーミドルウェアは実行されない。

方法3: Cloudflare Redirect Rules → 無限リダイレクトループ

試した設定

Cloudflareダッシュボードで以下のリダイレクトルールを設定:

  • 条件: ends_with(http.request.uri.path, "/") かつ http.request.uri.path ne "/"
  • アクション: Dynamic Redirect
  • Expression: concat(substring(http.request.uri.path, 0, -1))
  • ステータスコード: 301

発生した問題:ERR_TOO_MANY_REDIRECTS

ブラウザで「リダイレクトが繰り返し行われました」エラーが発生。

根本原因:Cloudflare Pagesの仕様

トレイリングスラッシュを強制的に追加するのがCloudflare Pagesの仕様である:

Cloudflare Pages is "an opinionated system that thinks trailing slashes should be there." — Cloudflare Community

実際に確認した挙動:

  • https://log.eurekapu.com/financial-quiz/jleague にアクセス
  • 自動的に https://log.eurekapu.com/financial-quiz/jleague/ に308リダイレクト

リダイレクトループの発生メカニズム:

1. ユーザーが /path/ にアクセス
2. 我々のルールが /path にリダイレクト (301)
3. Cloudflare Pagesが /path/ にリダイレクト (308)
4. 我々のルールが /path にリダイレクト (301)
5. ... 無限ループ

この挙動は無効化できないCloudflare Communityより)

方法4: _redirects ファイル → 動的パス変換ができない

全URLに対応するには全パスを列挙する必要があり、現実的ではない。

方法5: Cloudflare Workers → ループのリスクあり

Pagesの処理より前にCloudflare PagesのWorkerは実行されるが、Pagesの308リダイレクトとの相互作用で同様のループが発生するリスクがある。


推奨解決策:canonicalタグで対応

なぜcanonicalタグが最適か

Google公式ドキュメントによると:

方法シグナル強度用途
301リダイレクト最強ページを廃止・統合する場合
rel="canonical"強い重複ページが並存する場合
サイトマップ弱い補助的な方法

今回のケース:

  • /path/path/ の両方が存在し続ける(Cloudflare Pagesの仕様上、変更不可)
  • リダイレクトは無限ループを引き起こす
  • canonicalタグが唯一の現実的な解決策

canonicalタグの効果

SE Rankingによると:

canonicalタグは「重複ページが並存する場合に、どちらが正規版かを検索エンジンに伝える」ために使用する。 バックリンクのシグナルも統合される(301リダイレクトと同様の効果)。

実装内容

1. nuxt.config.ts の設定

site.trailingSlash: false を追加:

// nuxt.config.ts
export default defineNuxtConfig({
  site: {
    url: 'https://log.eurekapu.com',
    trailingSlash: false  // canonicalURLをスラッシュなしで生成(SEO重複対策)
  }
})

この設定により、@nuxtjs/sitemap が生成する sitemap.xml のURLもスラッシュなしになる。

2. canonicalタグ自動生成プラグイン

app/plugins/canonical.ts を作成:

/**
 * Canonical URL Plugin
 *
 * 全ページに自動的にcanonicalタグを追加する。
 * Cloudflare Pagesがトレイリングスラッシュを強制するため、
 * 301リダイレクトではなくcanonicalタグでSEO重複問題を解決する。
 */
export default defineNuxtPlugin({
  name: 'canonical',
  setup() {
    const route = useRoute()
    const siteUrl = 'https://log.eurekapu.com'

    // パスから末尾スラッシュを削除してcanonical URLを生成
    const getCanonicalUrl = (path: string) => {
      const cleanPath = path.replace(/\/$/, '') || '/'
      return `${siteUrl}${cleanPath}`
    }

    // SSR/SSGとクライアント両方で動作
    useHead({
      link: [
        {
          rel: 'canonical',
          href: () => getCanonicalUrl(route.path)
        }
      ]
    })
  }
})

3. 生成されるHTML

すべてのページに以下のようなcanonicalタグが自動追加される:

<link rel="canonical" href="https://log.eurekapu.com/financial-quiz/jleague">

確認済み: ローカル開発サーバーで http://localhost:3001/financial-quiz/jleague にアクセスし、HTMLに上記のcanonicalタグが含まれていることを確認。


各アプローチの比較(最終版)

アプローチ静的サイト対応CF Pages互換結果
Nuxt routeRules(関数)-ビルドエラー
サーバーミドルウェア-動作しない
Cloudflare Redirect Rules無限ループ
_redirects ファイル動的変換不可
Cloudflare Workers⚠️ループのリスク
canonicalタグ推奨

結論

Cloudflare Pagesの仕様上、トレイリングスラッシュの301リダイレクトは実現不可能。SEO的な重複問題をcanonicalタグで解決するのが唯一の現実的な解決策。

推奨アクション

  1. Cloudflare Redirect Rulesでリダイレクト設定 → 無限ループが発生するため不可
  2. 推奨: Nuxt SEOでcanonicalタグを自動設定(trailingSlash: false
  3. Sitemap確認: 生成されるsitemap.xmlのURLがスラッシュなしになっているか確認
  4. GSC対応: canonicalタグ設定後、1-2週間待つ(Googleの再クロールを待つ)

参考