HugoでGitHubのようなアクティビティカレンダーを実装したので、その方法を紹介しておきます。このパーツは汎用的に使えるので、どのHugoサイトでも簡単に導入できます(たぶん)。
完成イメージ
- 過去365日の投稿活動をヒートマップで可視化
- 投稿数に応じた5段階の色分け(GitHub風)
- 日付をクリックすると記事一覧を表示
- 月ラベルと曜日ラベル付き
- ダークモード対応
- レスポンシブデザイン

実装方法
2つの導入方法があります:
方法A: Hugo Module経由(推奨)
1. moduleの追加
プロジェクトルートで以下を実行:
hugo mod init github.com/yourusername/yoursite
hugo mod get github.com/mkontani/hugo-github-calendar-parts
2. config.tomlの設定
[params]
# カレンダーに表示するセクション(任意)
calendarSections = ["post", "diary", "tips"]
# トップページに表示するセクション(任意)
mainSections = ["post"]
[module]
[[module.imports]]
path = 'github.com/mkontani/hugo-github-calendar-parts'
モジュールのlayouts/ディレクトリは自動的にマウントされるため、追加の設定は不要です。
3. 表示したい場所で呼び出し
例えば、トップページに表示する場合は layouts/index.html に追加:
{{ partial "calendar/activity-calendar.html" . }}
moduleの更新
カレンダーを最新版に更新する場合:
hugo mod get -u github.com/mkontani/hugo-github-calendar-parts
hugo mod tidy
方法B: ファイルコピー方式
1. パーツファイルの作成
layouts/partials/calendar/activity-calendar.html を作成します。
このファイルには以下の要素が含まれています:
- Hugoテンプレート部分: 過去365日分の記事データを集計し、カレンダーのHTMLを生成
- CSS: GitHub風のスタイル(ライト/ダークモード対応)
- JavaScript: クリックイベント処理と月ラベルの動的生成
完全なコードはこちらを参照。
2. 設定の追加
config.toml に以下を追加:
[params]
# カレンダーに表示するセクション(任意)
calendarSections = ["post", "diary", "tips"]
# トップページに表示するセクション(任意)
mainSections = ["post"]
calendarSections を指定しない場合は、自動的に mainSections が使用されます。
3. 表示したい場所で呼び出し
例えば、トップページに表示する場合は layouts/index.html に追加:
{{ partial "calendar/activity-calendar.html" . }}
技術的なポイント
1. データ構造の設計
記事データを日付ごとに集計し、JSONとしてJavaScriptに渡しています:
{{- $postsByDate := dict -}}
{{- range where site.RegularPages "Type" "in" $calendarSections -}}
{{- $dateStr := .Date.Format "2006-01-02" -}}
{{- $posts := slice -}}
{{- if isset $postsByDate $dateStr -}}
{{- $posts = index $postsByDate $dateStr -}}
{{- end -}}
{{- $posts = $posts | append . -}}
{{- $postsByDate = merge $postsByDate (dict $dateStr $posts) -}}
{{- end -}}
2. グリッドレイアウト
CSS Gridを使って、縦7行(曜日)× 横に週が並ぶレイアウトを実現:
.calendar-grid {
display: grid;
grid-template-rows: repeat(7, 12px);
grid-auto-flow: column;
grid-auto-columns: 12px;
gap: 3px;
}
3. 月ラベルの動的生成
JavaScriptで各月の開始位置を計算し、適切な位置にラベルを配置:
days.forEach((day, index) => {
const date = day.getAttribute('data-date');
const month = date.substring(0, 7);
if (month !== lastMonth) {
const monthName = new Date(date).toLocaleDateString('en-US', { month: 'short' });
const weekColumn = Math.floor((index + emptyCount) / 7);
monthPositions.push({ month: monthName, column: weekColumn });
lastMonth = month;
}
});
4. 二重エンコード対策
Hugo の jsonify が環境によって二重エンコードする場合があるため、JavaScript側で対応:
postsData = JSON.parse(rawData);
if (typeof postsData === 'string') {
postsData = JSON.parse(postsData);
}
カスタマイズ例
色の変更
GitHub風の緑以外の色に変更したい場合は、CSS部分を修正:
.calendar-day.level-1 {
background-color: #9be9a8; /* お好みの色に変更 */
}
表示期間の変更
過去365日ではなく、別の期間を表示したい場合:
{{- $startDate := $endDate.AddDate 0 0 -364 -}} {{/* -364を変更 */}}
{{- $totalDays := 365 -}} {{/* 365を変更 */}}
セクションごとに色を変える
複数セクションで異なる色を使いたい場合は、データ収集時にセクション情報も保存し、CSSクラスを追加できます。
汎用性について
- Hugo標準機能のみ: 特殊なプラグインや外部依存なし
- テーマ非依存: どのHugoテーマでも動作するはず
- Hugo Module対応: moduleとして管理可能で更新も簡単
どちらの方法を選ぶべきか
- Hugo Module方式(推奨): 更新を追従したい場合、複数サイトで使う場合
- ファイルコピー方式: カスタマイズを自由にしたい場合、moduleを使いたくない場合
Hugo Moduleを使えば、カレンダーの更新を簡単に追跡でき、メンテナンスが楽になります。
今までSubmoduleで管理してましたが、いつの間にかHugo Moduleなんてのができてたんですね、これは便利。
まとめ
HugoでGitHub風のアクティビティカレンダーを実装してみました。記事の投稿頻度を視覚的に把握でき、過去の記事にアクセスしやすくなるUIパーツです。Hugo Moduleとして公開しているので、他のサイトでも簡単に導入できます。