TECH MEMO / FIELD NOTEGitHub PagesとGCSで作る、画像配信改善の実践メモ
はじめに
このサイトはHugoでビルドし、GitHub Pagesで公開しています。画像はGoogle Cloud Storage(GCS)から配信しています。
当初は問題なく表示できていましたが、記事が増えるにつれて表示速度が少しずつ落ちてきました。サイトデザインをリニューアルしたタイミングで、画像まわりの構成も見直すことにしました。
今回の改善では、PNG、JPEG、WebPが混在していた画像を棚卸しし、表示速度と運用方法を整理しました。LighthouseとブラウザのNetworkパネルで変更前の状態を記録し、ファイル数、容量、参照元を確認しています。
現状のパフォーマンスを測定する
まず、Lighthouseで現状を測定しました。
2026年9月7日11時00分58秒に、PageSpeed Insightsで https://urchin-hat.org/ を測定しました。実ユーザーのフィールドデータは「データがありません」と表示されたため、今回はラボ測定の結果を改善前のベースラインとして記録します。
カテゴリ別スコア
| カテゴリ | デスクトップ | モバイル |
|---|---|---|
| パフォーマンス | 65 | 58 |
| ユーザー補助 | 95 | 95 |
| おすすめの方法 | 100 | 100 |
| SEO | 100 | 100 |
パフォーマンス指標
| 指標 | デスクトップ | モバイル |
|---|---|---|
| First Contentful Paint(FCP) | 1.4秒 | 7.0秒 |
| Largest Contentful Paint(LCP) | 5.3秒 | 32.8秒 |
| Total Blocking Time(TBT) | 0ミリ秒 | 0ミリ秒 |
| Speed Index | 1.7秒 | 7.0秒 |
| Cumulative Layout Shift(CLS) | 0.139 | 0 |
デスクトップでもLCPが5.3秒、モバイルでは32.8秒に達しており、最初に表示されるコンテンツの読み込みが大きな課題です。一方でTBTは両方とも0ミリ秒で、CLSも低いため、JavaScriptの実行やレイアウトのずれよりも、画像の転送量とレンダリングを妨げるリソースを優先して確認することにしました。
診断結果では、過大なネットワークペイロード、画像配信の改善、レンダリングをブロックしているリソース、使用していないCSSが指摘されています。この結果を基準に、画像形式の統一、キャッシュ設定、配信方法を順に見直します。
画像を棚卸しする
使われていない画像は参照元を確認して整理しました。ただし、今後使う予定の画像は削除せず、不要な画像とは分けて管理しています。
GCSには公開済みページや外部からの直リンクが参照している可能性のある旧画像が残っています。旧PNG・JPEGの削除は、WebPへの切り替えとは別の作業として扱うことにしました。
原寸のままWebPへ変換する
今回は画像のピクセル寸法を変えず、形式だけを変換しました。PNGは可逆圧縮、JPEGは品質82で変換しています。
# PNG
cwebp -lossless -m 6 input.png -o output.webp
# JPEG
cwebp -q 82 -m 6 input.jpg -o output.webp
変換対象は84枚で、そのうち7枚は長辺が5,000pxを超えていました。最大サイズは5,118×2,632pxです。変換後に幅と高さを確認し、寸法が一致した画像だけ元形式のファイルを置き換えました。
変換前のGit管理ファイルと変換後のWebPを同じ画像ごとに比較すると、合計容量は約142.86MiBから101.41MiBになり、約29.02%削減できました。内訳はPNGが約28.02%、JPEGが約58.76%の削減です。ピクセル寸法は維持しているため、この差は画像形式とエンコード品質によるものです。
原寸画像をHugoのビルド時に一括変換すると、メモリ不足でWebPへのエンコードに失敗しました。そこで、事前に1枚ずつ変換し、Hugoでは再変換せずフィンガープリントだけを付与しています。
assets/ と static/ を使い分ける
| 配置先 | 用途 | Hugoでの扱い |
|---|---|---|
assets/images/ | 記事のバナー、本文画像、HTMLテンプレートの画像 | resources.Get で取得し、フィンガープリント付きで生成 |
static/images/ | SVG、ICO、CSSの background-image から直接参照するWebP | 加工せず、そのまま公開 |
通常の記事では、WebPを assets/images/ に追加し、MarkdownやFront Matterから /images/...webp で参照します。assets はパスに含めません。
image = "/images/banner/example.webp"

フィンガープリントとGCSのキャッシュ
HugoではWebPを再エンコードせず、resources.Get と fingerprint を使います。画像の内容が変わるとファイル名のハッシュも変わるため、長期キャッシュを設定しても古い画像が残り続けません。
GitHub Actionsでは、ハッシュ付きWebPをGCSへ差分同期します。既存オブジェクトは上書きせず、新しい画像だけをアップロードします。
gcloud storage rsync \
--recursive \
--no-clobber \
--exclude='^(?!.*\.[0-9a-f]{64}\.webp$).*' \
--cache-control='public,max-age=31536000,immutable' \
dist/images \
gs://YOUR_BUCKET_NAME/images
SVG、ICO、CSSから直接参照する固定WebPは別のキャッシュ期間で扱います。旧PNG・JPEGの削除やLifecycleルールの設定は、切り替え後の参照状況を確認してから別Issueで進めます。
今後の記事投稿で必要なこと
画像を使う記事では、元画像を原寸WebPへ変換し、assets/images/ に配置して /images/...webp で参照します。hugo server -D と本番相当のビルドで表示を確認し、記事とWebPをGitへ追加します。
ハッシュ付きファイル名の作成や、通常の記事投稿ごとのGCSへの手動アップロードは必要ありません。mainブランチへの反映後、Workflowが差分同期を行います。
おわりに
画像形式の変換だけでなく、画像の棚卸し、ビルド負荷、キャッシュ、GitHub Actionsの転送量を一つの流れとして整理しました。今後の画像追加手順をWebPと assets/images/ に統一したことで、配信先を意識せず記事を投稿できるようになりました。
