PaperModテーマを使っていたHugoブログを、Tailwind CSS v4ベースの独自テーマに移行し、Hugo公式テーマギャラリーに登録しました。その過程で遭遇した問題と対処を記録します。
Hugo + Tailwind CSS v4の統合
Hugo 0.157以降ではcss.TailwindCSSパイプラインがサポートされています。Tailwind v4は@import "tailwindcss"ベースの構文に変わっており、tailwind.config.jsは不要です。
/* assets/css/main.css */
@import "tailwindcss";
@theme {
--color-primary: #2563eb;
--font-sans: "Inter", sans-serif;
}
@variant dark (&:where(.dark, .dark *));
テンプレート側ではcss.TailwindCSSを呼び出します。
{{ with resources.Get "css/main.css" | css.TailwindCSS }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{ end }}
ビルド時に注意が必要な点として、@tailwindcss/cliへのパスが通っている必要があります。Cloudflare Pagesなどのデプロイ環境では以下のようにPATHを設定します。
# hugo.yaml (build settings for Cloudflare Pages)
build:
command: "PATH=$PWD/node_modules/.bin:$PATH hugo --minify && npx pagefind --site public"
PaperModからの移行方法
テーマの切り替えはhugo.yamlのtheme:を変更するだけで済むため、独自テーマを開発しながらいつでもPaperModに戻せます。
# hugo.yaml
theme: hugo-theme-flavor # PaperModに戻す場合は "PaperMod" に変更
移行作業は主に、layouts/ディレクトリのオーバーライドファイルをテーマ内に統合する作業でした。PaperModの構造に依存していた箇所(パーシャルの呼び出しやCSS変数名)を独自テーマの構造に合わせて書き換えています。
スタッキングコンテキストの問題
position: stickyを指定したヘッダー内にposition: fixedのモバイルメニューを配置したところ、z-indexを大きな値に設定してもメニューが他の要素の背面に表示される現象が発生しました。
これはCSSのスタッキングコンテキストの仕様によるものです。position: stickyの要素は新しいスタッキングコンテキストを形成するため、その子要素のz-indexは親のコンテキスト内でのみ有効になります。
対処として、モバイルメニューのDOM要素を<header>の外(<body>直下)に移動しました。
<header class="sticky top-0 z-40">
<!-- ヘッダー内容 -->
</header>
<!-- メニューをheaderの外に配置 -->
<div id="mobile-menu" class="fixed inset-0 z-50 hidden">
<!-- メニュー内容 -->
</div>
Tailwind v4の@layerの優先度
Tailwind v4では@layer components内のルールはレイヤー外のルールより優先度が低くなります。これはCSS Cascade Layersの仕様に基づくものです。
当初メディアクエリを@layer components内に記述していましたが、レイヤー外のTailwindユーティリティに上書きされてしまいました。メディアクエリをレイヤー外に配置することで意図通りの動作になりました。
/* NG: @layer内だとユーティリティに負ける */
@layer components {
@media (max-width: 768px) {
.sidebar { display: none; }
}
}
/* OK: レイヤー外に配置 */
@media (max-width: 768px) {
.sidebar { display: none; }
}
Hugo公式テーマギャラリーへの登録
Hugo Themesへの登録には以下の要件があります。
theme.toml— テーマのメタデータ(name, description, tags, min_version等)go.mod— Hugoモジュールとしての定義exampleSite/— テーマの動作確認用サイト- スクリーンショット —
images/screenshot.png(1500×1000px、3:2比率)とimages/tn.png(サムネイル) README.md— 導入手順とカスタマイズ方法LICENSE— OSS ライセンス
登録はhugoThemesSiteBuilderリポジトリにPRを出す形式です。hugo-themes.txtにテーマのGitHubリポジトリURLを追加します。
テーマの汎用化
個人ブログ用に作ったテーマを公開するにあたり、以下の作業が必要でした。
- i18n化: テンプレート内のハードコード文字列を
i18n/ja.yaml、i18n/en.yamlに移動し、{{ i18n "readMore" }}で参照 - フォント設定の外部化:
hugo.yamlのparams.fontsからGoogleフォントを指定可能に - アクセシビリティ: タッチターゲット44px以上の確保、テキストのコントラスト比4.5:1以上
- JSON-LD:
safeHTMLではなくjsonifyで構造化データを出力(XSS対策) - Pagefind:
params.pagefind.enabledで条件付き読み込み(テーマ利用者がPagefindを使わない場合に対応)
並列レビュー体制
テーマ公開前のレビューでは、9つの観点を並列で検証しました。
- アクセシビリティ(WCAG 2.1 AA準拠)
- SEO(メタタグ、構造化データ)
- i18n(多言語対応の網羅性)
- CSS(未使用スタイル、レスポンシブ)
- パフォーマンス(Lighthouse スコア)
- セキュリティ(CSP、外部リソース)
- Hugo互換性(最小バージョン、非推奨関数)
- テーマ規約(theme.toml、exampleSite)
- ドキュメント(README、CHANGELOG)
各観点で指摘事項を洗い出し、一括で修正する方式を取りました。個別に修正→レビューを繰り返すより効率的だったように思います。