Hugo と GitLab で静的 Web サイトを作る
はじめに
Djangoや Northflankで作るのは、DB を持ち処理が動く Web システムでした。一方、会社案内・サービス紹介・ドキュメント・コラムのように、更新頻度が低く、サーバー側で動く処理が要らないサイトは、静的サイトが向いています。速く、安く、壊れにくいためです。
この記事では Hugo(静的サイトジェネレーター)でサイトを作り、GitLab のリポジトリと GitLab Pages で公開する流れを整理します。実はこのサイト自体がその構成です。
静的サイトジェネレーターとは
Markdown で書いた原稿と、HTML のテンプレートを組み合わせて、素の HTML ファイルを事前に生成する仕組みです。配信するのは出来上がった HTML/CSS/JS だけなので、実行環境も DB も要らず、CDN に置けば高速で、攻撃対象も小さくなります。
Hugo は Go 製で、ビルドが非常に速いのが特徴です。数百ページでも一瞬で生成されます。
Hugo の構成
プロジェクトはおおむね次のディレクトリでできています。
サイト/
├── hugo.toml … サイト全体の設定(baseURL、テーマ、パラメータ)
├── content/ … 原稿(Markdown)。ここの構造が URL 構造になる
├── layouts/ … テンプレート。テーマの上書きもここ
├── themes/ … 導入したテーマ
├── static/ … そのまま配信されるファイル(画像・CSS など)
└── public/ … `hugo` で生成される出力(Git 管理はしない)
hugo server… ローカルにプレビュー用サーバーを立てる。保存すると即座に反映hugo…public/に本番用の HTML を生成
コンテンツの書き方
各 Markdown ファイルの先頭に フロントマター(メタ情報)を書きます。
+++
title = 'ページタイトル'
date = '2026-09-09T09:00:00+09:00'
draft = false
description = '検索結果や SNS に出る要約'
+++
本文をここに書く。
content/about.md→/about/、content/column/foo.md→/column/foo/のように、ディレクトリがそのまま URL になるhugo new column/bar.mdで雛形から新規作成draft = trueの記事は本番ビルドに含まれない。未来のdateも既定では公開されない
テンプレートとテーマ
表示は Go のテンプレートで組みます。基本の 3 つは、
baseof.html… 全ページ共通の骨組みlist.html… 一覧ページ(セクションのトップなど)single.html… 個別ページ
共通部分は partials/ に切り出します。テーマを導入し、変えたいテンプレートだけを自分の layouts/ に置くと上書きできます。CSS も、テーマの上に自分の 1 枚を重ねる形にしておくと管理が楽です。
GitLab に置いて公開する
リポジトリを GitLab に作り、.gitlab-ci.yml を置くと、GitLab CI がビルドし、GitLab Pages が public/ を配信します。このサイトで使っている設定はおおよそ次のとおりです。
# Hugo extended + Dart Sass + Node.js + git を同梱するイメージ
image: hugomods/hugo:dart-sass-node-git-0.161.1
variables:
HUGO_ENV: production
TZ: Asia/Tokyo
GIT_DEPTH: "0" # .Lastmod / .GitInfo 用に全履歴を取得
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .hugo_cache/
- node_modules/
pages:
stage: deploy
script:
- npm ci
- hugo --gc --minify
artifacts:
paths:
- public
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
ポイントは次の点です。
pagesという名前のジョブでpublic/を artifact に出すと、GitLab Pages がそれを公開する- イメージのバージョンを固定する。Hugo は挙動が変わることがあるため、CI とローカルで同じ版に揃える(Docker の考え方と同じ)
GIT_DEPTH: "0":Hugo でページの更新日をコミット履歴から取る(enableGitInfo)には全履歴が要る。浅いクローンだと.Lastmodが正しくならない- キャッシュ:
.hugo_cache/とnode_modules/を残すとビルドが速い rules:デフォルトブランチへの push のときだけ公開する
独自ドメインと HTTPS
GitLab の Settings → Pages で独自ドメインを追加し、DNS を検証すると、Let’s Encrypt の証明書が自動で発行・更新されます。hugo.toml の baseURL は本番ドメインに合わせておきます。
ローカル開発
hugo server を直接動かすのが最も手軽ですが、CI と同じ Hugo イメージを Docker で動かしてプレビューする手もあります。こうすると、手元・CI・本番でビルド環境が完全に一致します。
docker run --rm -it -p 1313:1313 -v "$PWD":/src \
hugomods/hugo:dart-sass-node-git-0.161.1 \
server --bind 0.0.0.0
レビューの進め方
原稿の追加・修正は ブランチと Merge Requestで進めます。差分がレビューでき、合意したら default ブランチへ merge、CI が走って本番へ反映、という流れです。公開前に確認したいときは、ブランチのビルド結果を artifact から取得するか、レビュー用の Pages 環境を用意します。
関連記事
- Git と GitHub の基本 — ブランチと Merge Request でのレビュー
- Docker の基本 — CI とローカルでイメージを固定する考え方
- LaTeX コンパイルを GitHub Actions で自動化する — CI でビルドして成果物を配る(GitHub 側)
- Django による Web システムの構築 — 動く処理が要るサイトはこちら
- Northflank — アプリと DB をデプロイする先
まとめ
- 動く処理の要らないサイトは静的サイトが有利。Hugo は Markdown + テンプレートで素の HTML を高速生成する。
content/の構造が URL、フロントマターがメタ情報。テーマは上書きで必要な所だけ変える。- GitLab では
pagesジョブでpublic/を artifact に出せば Pages が配信。イメージ固定・GIT_DEPTH:0・キャッシュが要点。 - 独自ドメインと HTTPS は Pages の設定で完結。更新はブランチ+ Merge Request で。
このコラムも、記事の Markdown を書いて push するだけで、CI がビルドして公開する ── という形で運用しています。