Webサイトのリダイレクト設計と設定の整理

はじめに

Hugo で構築した多言語サイトを運用していると、いくつかの「見えない問題」が積み重なっていることに気づく。タグページが生成されても誰も見ていない。ページネーションが正しくレンダリングされていない。そして英語記事がなくても日本語にリダイレクトされない。

本稿は、そうした問題を一つずつ整理した作業の記録だ。

不要なページを生成しない

タグページとセクションページの無効化

Hugo はデフォルトで taxonomy(タグ・カテゴリ)のページを生成する。しかし実際には使われておらず、ビルド成果物に含まれるだけの存在だった。

hugo.toml に以下を追加することで無効化できる。

disableKinds = ["taxonomy", "term", "section"]

taxonomy はタグ一覧(~/tags/~)、~term~ は個別タグページ(~/tags/golang/~)、~section~ はセクション一覧(~/ja/posts/~)に対応する。ホームページ(~/ja/~)は home という別の kind であるため、これを追加しても影響しない。

ページネーションの抑制

/posts/page/2/ 以降のページネーションも、レイアウトが正しく実装されていないため表示が崩れていた。根本的なレイアウト修正ではなく、ページネーション自体を発生させないという判断をした。

Hugo v0.128 以降では [pagination] セクションで設定する。

[pagination]
  pagerSize = 999999

記事数より大きな値を指定することで、全記事が1ページに収まり、ページネーションページが生成されなくなる。

多言語リダイレクトの設計

目標

このサイトは日本語・英語・ポルトガル語・インドネシア語・トルコ語の5言語に対応している。記事は常に日本語版が存在するが、他言語版はないことも多い。

目標はシンプルだ。他言語版が存在しない URL にアクセスがあった場合、対応する日本語版へリダイレクトする。他言語版が存在する場合はそのまま表示する。

ワイルドカード方式の失敗

最初に試みたのは static/_redirects にワイルドカードルールを書く方法だ。

/posts/*    /ja/posts/:splat 301
/pt/posts/* /ja/posts/:splat 301
/id/posts/* /ja/posts/:splat 301
/tr/posts/* /ja/posts/:splat 301

Netlify は静的ファイルをリダイレクトより優先して配信するため、この方式が成立する。しかし Cloudflare Pages は異なる。リダイレクトルールが静的ファイルより先に評価されるため、英語版が存在する記事まで日本語にリダイレクトされてしまった。

Hugo テンプレートによる動的生成

解決策は、ビルド時に「翻訳が存在しない言語のルールだけ」を生成することだ。

Hugo のカスタム出力フォーマットを使う。~hugo.toml~ に以下を追加する。

[mediaTypes."text/redirects"]
  suffixes = [""]

[outputFormats.redirects]
  baseName = "_redirects"
  isPlainText = true
  mediaType = "text/redirects"
  notAlternative = true

[outputs]
  home = ["HTML", "RSS", "redirects"]

layouts/index.redirects テンプレートでは、日本語記事を全件走査し、各言語の翻訳が存在するかを確認する。存在しない言語にのみリダイレクトルールを出力する。

{{- $defaultLang := (index site.Languages 0).Lang }}
{{- if eq site.Language.Lang $defaultLang }}
{{- $jaSite := index (where site.Sites "Language.Lang" "ja") 0 }}
{{- range $jaSite.RegularPages }}
{{- $jaURL := .RelPermalink }}
{{- $basePath := strings.TrimPrefix "/ja" $jaURL }}
{{- $translationLangs := slice }}
{{- range .Translations }}{{- $translationLangs = $translationLangs | append .Lang }}{{- end }}
{{- range site.Sites }}
{{- $lang := .Language.Lang }}
{{- if and (ne $lang "ja") (not (in $translationLangs $lang)) }}
{{- if eq $lang $defaultLang }}
{{ $basePath }} {{ $jaURL }} 301{{ else }}
/{{ $lang }}{{ $basePath }} {{ $jaURL }} 301{{ end }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

site.DefaultContentLanguage は Hugo テンプレートでは直接参照できないため、~(index site.Languages 0).Lang~ で代替している点に注意が必要だ。

static/ ディレクトリのマウント問題

落とし穴

_redirectsstatic/ に配置したにもかかわらず、ビルド後の public/ に現れないという問題が発生した。

原因は hugo.toml[[module.mounts]] 設定にある。Hugo はモジュールマウントを1件でも定義すると、そのコンポーネントのデフォルトマウントを無効化する。このサイトでは static/game/scroll などへのカスタムマウントが設定されていたため、~static → static~ のデフォルトマウントが失われていた。

修正

[module] セクションに明示的なマウントを追加する。

[[module.mounts]]
  source = "static"
  target = "static"

content → content のマウントはすでに定義されていたが、~static~ は抜け落ちていた。

Makefile の保持パターン

ビルド後、Makefile は index.html.css など必要なファイル以外を削除する。~_redirects~ もこの削除対象になるため、保持パターンに追加した。

grep -v -E '(index\.html|404\.html|index\.xml|sitemap\.xml|\.css|\.js|\.wasm|_redirects)$$' \
  .filelist.txt > .removefilelist.txt

まとめ

今回の作業で変更したのは4ファイルだ。

  • hugo.tomldisableKinds, pagination, 出力フォーマット, module.mounts
  • layouts/index.redirects — 動的リダイレクトルール生成テンプレート(新規)
  • Makefile_redirects を保持パターンに追加
  • static/_redirects — 削除(テンプレートに統合)

変更量は小さいが、影響は明確だ。不要なページが消え、リダイレクトは翻訳のない記事にだけ適用される。「ワイルドカードで一括」という簡単な方法が Cloudflare Pages では通用しないことを学んだのも、今回の収穫の一つだった。

作成日