このサイトはHugo
で構築しています。
ベースはRobust
というテーマで、これをフォークしてrobust-iniwa
というリポジトリで管理し、各サイトからは Git submodule として取り込んでいます。
この記事では、現在の robust-iniwa に入っている主なカスタマイズと、それを支えるサイト側(このリポジトリ)の構成についてまとめます。
コードはどんどん変わるので、最新はGitHub
を見るのが早いです。この記事は雰囲気を掴む用です。
現在の構成
最初は「themes/robust 配下は触らず、/layouts/ に上書きファイルを置く」運用にしていましたが、姉妹サイト(diary.iniwach.com
)と共通の改造が増えてきたので、テーマ自体をフォークして共有することにしました。
iniwach.com/
├── config/
│ ├── development/config.toml
│ └── production/config.toml
├── content/
├── static/
└── themes/
└── robust-iniwa/ ← submodule (https://github.com/iniwa/hugo_theme_robust-iniwa)
├── archetypes/
├── assets/
├── layouts/
└── static/css|js/
主な指針は次の通り。
- テーマ全体に効くカスタマイズは
themes/robust-iniwa/に置き、両サイトで共有 - サイト固有のもの(記事、画像、
config.toml、static/robots.txtなど)はサイトのリポジトリに置く - 設定は
config/development/とconfig/production/で分割し、hugo serverと本番ビルドで使い分ける
robust-iniwa 側では、上流(dim0627/hugo_theme_robust)に対してファイル単位で [mod] [new] [removed] のコメントマーカーを付けて差分を追えるようにしています。
サイドバーの構成
Author / Archives / Tags / Categories / Latests / Memos の順で並べています。
themes/robust-iniwa/layouts/_default/baseof.html でこの順に partial を呼び出している形です。
Author欄
Font Awesome
のアイコンで Twitter(X)、YouTube、Twitch、メールへのリンクを並べています。
v6 系を使うため、meta.html から CDN を読み込んでいます(SRI ハッシュ付き)。
config.toml の [params.author] を埋めるとそのまま反映されます。
|
|
サイドバーのアイコン部分は themes/robust-iniwa/layouts/partials/author.html で、上流の Twitter アイコンを X (fa-x-twitter) に差し替え、mail/Twitch/YouTube のエントリを追加しています。
月別アーカイブ・Tags
Taxonomy
で、archive と tag の2つを定義。Categories は使わなくなったので消しました。
|
|
各記事のフロントマターで下のように書けば、勝手に年月別アーカイブが生成されます。
archives:
- 2025-02
サイドバー描画用に themes/robust-iniwa/layouts/partials/archives.html を新規作成し、上流の taxonomy.html を流用する形にしています。
Latests
最新記事一覧。上流は10件固定だったので、Site.Params.latests_count で件数を可変にしました。未設定なら50件です。
[params]
latests_count = 50
Memos(自前ホスティングのメモ連携)
別途立てている Memos
サーバーから最新メモを取ってきて、サイドバーに表示するようにしました。
Site.Params.memos_url を設定したときだけ partial が描画されます。
外部スクリプト(marked, DOMPurify)はバージョン固定 + SRI ハッシュ付きで読み込み、サニタイズしてから描画しています。
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.min.js"
integrity="sha384-..." crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"
integrity="sha384-..." crossorigin="anonymous"></script>
サイドバーの追従スクロール
PC 表示時はサイドバーが画面に追従するようにしています。
昔は custom.css に直書きしていましたが、今は themes/robust-iniwa/static/css/layout.css に分離。スクロールバーは Webkit/IE/Firefox それぞれの方法で非表示にしています。
ヘッダーとフッター
スクロール連動でヘッダーを隠す
よく見るアレ。下スクロールでヘッダーが消え、上スクロールで再表示されます。
baseof.html 末尾に小さい JS、layout.css に .l-header.is-hidden の transform を仕込む構成です。
フッターに Notice / Privacy
baseof.html の <footer> に /notice と /privacy へのリンクを追加。
それぞれ content/notice.md と content/privacy.md として用意しています。
記事メタデータの取り扱い
更新日(Lastmod)の表示
summary.html と list.html の両方で、Date != Lastmod のときに更新アイコン付きで日付を出すようにしています。
<li><i class="fas fa-calendar"></i><time datetime="...">{{ .Date.Format ... }}</time></li>
{{ if ne .Date .Lastmod }}
<li><i class="fas fa-sync"></i><time datetime="...">{{ .Lastmod.Format ... }}</time></li>
{{ end }}
dateformat は Site.Params.dateformat で "2006/01/02" 形式に統一しています。
archetype
hugo new ○○.md で生成される雛形。サイトリポジトリ側の archetypes/default.md に置いてあります。
|
|
description は OGP 用に必須で入れる方針なので、空欄を最初から差し込んでいます。
OGP / Twitter Card / 構造化データ
当初は記事一覧ページにだけ手書きで OGP メタタグを追加していましたが、今はテーマ側で全ページ自動対応するようにしました。
themes/robust-iniwa/layouts/partials/meta.html の主なポイント。
IsHome/IsPage/ その他で OGP の中身を出し分けdescriptionは frontmatter のdescription優先、なければSummaryを160文字で truncate- 記事画像は Page Bundles に対応(
Resources.GetMatchで記事フォルダ内の画像を解決) og_worker_urlが設定されていれば、Cloudflare Workers + Satori で OG 画像を動的生成- localhost / 127.0.0.1 の URL は Workers に渡せないので除外
OG 画像生成側のコードは別記事にする予定ですが、?title=...&bg=...&site=...&v=... で記事タイトルや背景画像、サイト名、Lastmod のキャッシュバスター を渡しています。
構造化データ(JSON-LD)
themes/robust-iniwa/layouts/partials/single_json_ld.html で BlogPosting を出力。上流は NewsArticle でしたが、ブログ記事なら BlogPosting の方が適切です。
description / author / dateModified などひととおり整備しています。
アクセス解析・コメント・収益化
| 機能 | どこに書く | 状態 |
|---|---|---|
| Google Analytics | [services.googleAnalytics] |
本番ビルド時のみ計測(DEV 環境では無効) |
| Disqus | [services.disqus] |
上流の single.html がそのまま使える |
| Umami | [params.umami] |
website_id を入れたときだけ計測スクリプト挿入 |
| Google AdSense | [params] adsense_client_id |
hugo.IsProduction のときだけ <head> に挿入 |
[params.umami] の構造はこんな感じ。
[params.umami]
website_id = "xxxxxxxx-..."
script_url = "https://(自前ドメイン)/script.js"
AdSense の <script> タグは meta.html の中で Site.Params.adsense_client_id と hugo.IsProduction の AND を取ってから挿入しているので、開発環境では発火しません。
ショートコード
themes/robust-iniwa/layouts/shortcodes/ に下記を入れています。
accordion
<details> を使った折りたたみ。
ここをクリックで展開できます。
{< accordion title="ここをクリックで展開" >}
ここに展開後の内容
{< /accordion >}
※実際はカッコは2つです。
img-view
画像をグリッドで並べる用。
{< img-view "a.jpg" "b.jpg" "c.jpg" >}
{< img-view 2 "wide1.jpg" "wide2.jpg" >}
先頭に数値を入れると列数を指定できる(デフォルト3列)ようにしてあります。WebP サムネを自動生成して <picture> で出すので、フルサイズ原本は別ロードされません。
tip
ホバー / フォーカスでツールチップを出す注釈。
これは {< tip "ここにポップアップ文" >}対象テキスト{< /tip >} です。
スタイルは static/css/tip.css、開閉ロジックは static/js/tooltip.js。
外部リンク
新しいタブで開く + アフィリエイト sponsored 自動付与
themes/robust-iniwa/layouts/_default/_markup/render-link.html で Markdown のリンクをカスタムレンダー。
http(s)://で始まるリンクはtarget="_blank" rel="noopener"を付与- Amazon・楽天・e☆イヤホン・A8・LinkSynergy 等のアフィリエイト系ドメインを含む場合は
rel="sponsored noopener"に切り替え(Googleガイドライン準拠)
<a href="..." rel="sponsored noopener" target="_blank">...</a>
ドメイン一覧は配列で持っているので、必要に応じて足せます。
CSS の管理
昔は \layouts\partials\custom.css に全部詰めていましたが、テーマフォーク後にファイル分割しました。
static/css/variables.css… カラー変数とサイズ。テーマバリアント切替もここstatic/css/layout.css… ヘッダー・サイドバー・フッターと全体レイアウトstatic/css/content.css… 記事本文の見出し・段落・コードブロック・accordion などstatic/css/pagination.css… ページネーションstatic/css/grid.css…img-viewのグリッドstatic/css/tip.css… ツールチップstatic/css/memos-style.css… Memos サイドバー用
テーマバリアント
baseof.html の <body data-theme="..."> 属性に Site.Params.theme_variant を入れていて、variables.css で [data-theme="garden"] / [data-theme="tech"] ごとに変数値を上書きしています。
このサイトは tech、姉妹サイトの diary は garden です。
[params]
theme_variant = "tech" # or "garden"
SyntaxHighlight・コードブロック
config.toml の [markup.highlight] で Hugo 内蔵の Chroma を使う設定にしています。
|
|
記事内では Markdown 記法でそのまま書けます。
```text {linenos=true,hl_lines=[1,"3-4"],linenostart=1}
ここにコードを記入
ハイライト指定や行番号開始位置を渡せる
```
詳細はHugoのドキュメント へ。
robots.txt と sitemap.xml
タグ・アーカイブ・カテゴリ・下書きをクロール対象から外したかったので、enableRobotsTXT = false にして自前の static/robots.txt を使う構成にしました。
User-agent: *
Disallow: /categories/
Disallow: /tags/
Disallow: /archives/
Disallow: /_draft/
Sitemap: https://iniwach.com/sitemap.xml
sitemap も同じディレクトリを除外したかったので、themes/robust-iniwa/layouts/sitemap.xml で Permalink をフィルタする独自テンプレートに差し替え。
{{ range .Data.Pages }}
{{ if or (in .Permalink "/tags/") (in .Permalink "/categories/") (in .Permalink "/archives/") (in .Permalink "/_draft/")}}
{{ else }}
<url>
<loc>{{ .Permalink }}</loc>
...
</url>
{{ end }}
{{ end }}
404ページ
上流の 404 はタイトルだけなので拡張。「トップへ戻る」リンクと最新記事5件を表示するようにしています。
themes/robust-iniwa/layouts/404.html 。
おわりに
最初は「themes 配下を触らない」運用でしたが、姉妹サイトとの共通化、上流との差分管理、デプロイ後のホットフィックスのしやすさ等を考えると、フォーク + submodule の方が圧倒的に楽でした。
hugo server で常にローカル確認しながら進められるのは引き続き気持ちいいです。AI を併用して書いていますが、上流との差分箇所には [mod] [new] [removed] のマーカーを残す運用にしているので、後から見返すときも比較的読みやすい状態を保てています。
更新履歴
- 2025/02/06
- コードブロック書式の追加
- コンテンツの横幅についてを追加
- RSSフィードの対応
- 2025/02/07
- Robots.txtについて追加
- 2025/02/10
- ogpタグについて追加
- 記事一覧に更新日時の追加
- 2025/07/17
- リンクを新しいタブ開くを追加
- custom.cssを修正
- 2025/09/02
- 画像のグリッド表示を追加
- 2025/11/27
- custom.cssを更新
- ヘッダー追従の実装
- 2025/12/05
- テーマのサブモジュール化
- 2026/04/28
- 記事全体を現状の robust-iniwa の構成に合わせて全面リライト
- OGP(Page Bundles・Satori Workers連携)、JSON-LD、Memos、Umami、AdSense、tip ショートコード、テーマバリアント、CSS分割、外部リンクの sponsored 付与、404 ページ拡張などを追加
関連記事
参考にした記事
zzzmisa’s blog : HugoのテーマRobustのカスタマイズver3
syocky tech blog : Hugoテーマ「Robust」をカスタマイズする
親方の徒然なる日々 : HugoでFontAwesomeを使用してみた。
gkzz.dev : アコーディオンメニューをHugoで作った
テストウフ : Hugoのブログにページネーションを追加した
TelBouzu’s Blog : HugoのRobustでfaviconとかAdsenseの登録とか
OLD SUNSET DAYS : Hugoでrobots.txtとsitemap.xmlを作る
アロハル : HUGOでmeta descriptionなどのmetaタグを最適化する方法
k-kazがHugoで遊ぶサイト : Hugoで外部サイトを新しいタブで開きたい。
五里霧中 : GridレイアウトをCSSを利用したものに変更しました