このサイトは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.tomlstatic/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] を埋めるとそのまま反映されます。

1
2
3
4
5
6
7
8
9
[params.author]
thumbnail      = "images/profile/250202Author.png"
name           = "いにわ"
description    = "技術的な備忘録サイト。…"
twitter        = "https://twitter.com/iniwach"
twitter_handle = "@iniwach"
YouTube        = "https://www.youtube.com/@iniwach"
Twitch         = "https://www.twitch.tv/iniwach"
mail           = "GoogleフォームURL"

サイドバーのアイコン部分は themes/robust-iniwa/layouts/partials/author.html で、上流の Twitter アイコンを X (fa-x-twitter) に差し替え、mail/Twitch/YouTube のエントリを追加しています。

月別アーカイブ・Tags

Taxonomy で、archivetag の2つを定義。Categories は使わなくなったので消しました。

1
2
3
[taxonomies]
tag     = "tags"
archive = "archives"

各記事のフロントマターで下のように書けば、勝手に年月別アーカイブが生成されます。

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.mdcontent/privacy.md として用意しています。

記事メタデータの取り扱い

更新日(Lastmod)の表示

summary.htmllist.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 }}

dateformatSite.Params.dateformat"2006/01/02" 形式に統一しています。

archetype

hugo new ○○.md で生成される雛形。サイトリポジトリ側の archetypes/default.md に置いてあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
---
title: "{{ .TranslationBaseName | humanize | title }}"
description: ""
date: "{{ dateFormat "2006-01-02" .Date }}"
Lastmod: "{{ dateFormat "2006-01-02" .Date }}"
thumbnail: images/thumbnail/default_thumbnail.png
draft: true
toc: true
archives:
  - {{ dateFormat "2006-01" .Date }}
tags:
  - 
---

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.htmlBlogPosting を出力。上流は 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_idhugo.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.cssimg-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 を使う設定にしています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[markup]
  [markup.highlight]
    anchorLineNos     = false
    codeFences        = true
    guessSyntax       = false
    lineNoStart       = 1
    lineNos           = false
    lineNumbersInTable = true
    noClasses         = true
    style             = 'monokai'
    tabWidth          = 4
    wrapperClass      = 'highlight'

記事内では 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.xmlPermalink をフィルタする独自テンプレートに差し替え。

{{ 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 ページ拡張などを追加

関連記事

Hugoブログを運用してく上でちょっと便利な事達

参考にした記事

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を利用したものに変更しました