はじめに
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 301Netlify は静的ファイルをリダイレクトより優先して配信するため、この方式が成立する。しかし 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/ ディレクトリのマウント問題
落とし穴
_redirects を static/ に配置したにもかかわらず、ビルド後の 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.toml—disableKinds,pagination, 出力フォーマット,module.mountslayouts/index.redirects— 動的リダイレクトルール生成テンプレート(新規)Makefile—_redirectsを保持パターンに追加static/_redirects— 削除(テンプレートに統合)
変更量は小さいが、影響は明確だ。不要なページが消え、リダイレクトは翻訳のない記事にだけ適用される。「ワイルドカードで一括」という簡単な方法が Cloudflare Pages では通用しないことを学んだのも、今回の収穫の一つだった。