Hugoブログの画像をGitHubに置かず、Cloudflare R2で管理することにした

読了 約6分
Hugoブログの画像をGitHubに置かず、Cloudflare R2で管理することにした

最初は画像もリポジトリに入れればいいと思っていた

HugoはMarkdownと画像をまとめてGitHubで管理できるのが強みのひとつです。記事ごとにフォルダを作り、その中に index.md と画像をまとめて置く「Page Bundle」という構成が使えるので、記事と素材の対応が分かりやすくなっています。

content/posts/
  2026-05-01-my-article/
    index.md
    cover.webp
    diagram.png

この構成は直感的で、最初はこれで十分だと思っていました。

ところが記事が増えていくにつれて、アイキャッチ画像・サムネイル・OGP画像を毎回用意するようになります。1記事あたり3〜5枚の画像が増えていくと、リポジトリのサイズがじわじわ膨らんでいくのではないかという、不安に駆られました。まだ、ほとんど記事書いてないですけど。Markdownファイル本体は数KBなのに、画像だけで数MBという状況はよくないのでは?と思うようになりました。

GitHubリポジトリが重くなるのが気になった

Gitはテキストファイルの差分管理が得意なツールです。しかしバイナリファイル(画像・動画など)は差分を持たず、変更のたびにファイル全体が履歴として積み上がります。

たとえばアイキャッチ画像を差し替えると、古い画像も新しい画像もGitの履歴に残ります。git clone するたびにその全履歴を取得することになるので、リポジトリが年単位で成長すると無視できないサイズになります。

CI/CDやCloudflare Pagesのビルドも同様で、ビルドのたびにリポジトリ全体をチェックアウトします。画像が増えれば増えるほど、ビルド時間に影響する可能性があります。

「ブログ本文の変更履歴」と「画像アセットの保管場所」は、そもそも性質が違います。そう気づいてから、分けて管理したほうがすっきりすると思いました。

Cloudflare R2を画像置き場にした

Cloudflare R2はCloudflareが提供するオブジェクトストレージです。Amazon S3と互換性のあるAPIを持ちつつ、エグレス料金(データ転送料)がかからないのが特徴で、個人ブログの画像配信には手頃な選択肢です。

無料枠は、ストレージ10GB・Class Aオペレーション(書き込み)100万回/月・Class Bオペレーション(読み込み)1000万回/月となっています。個人ブログで画像を置く用途であれば、まずストレージが上限に達することはありません。読み込み回数についても、1000万回というのは1日あたり約33万リクエストに相当します。そもそも個人ブログにそれだけのPVがあれば、もうブログで飯が食えています。このブログには当然そんなPVはないので、無料枠で一生やっていける気がしています。

このブログはCloudflare Pagesで配信しているので、Cloudflare側に画像も寄せると運用がまとまりやすくなります。R2バケットにカスタムドメインを設定することで、画像URLを https://images.keyuki.net/... という形で統一できます。

記事のフロントマターには画像URLだけを書きます。画像本体はリポジトリに存在しません。

featured_image: https://images.keyuki.net/uploads/2026/05/sample-eyecatch.webp
thumbnail_image: https://images.keyuki.net/uploads/2026/05/sample-thumb.webp
images:
  - https://images.keyuki.net/uploads/2026/05/sample-og.jpg

Hugo側ではURLを参照するだけにした

移行後のリポジトリには、記事本文・テンプレート・CSS・設定ファイルだけが残ります。画像本体はR2に置き、Hugoはそこへのパスを参照するだけになりました。

フロントマターで使っているフィールドの役割も整理しています。

  • featured_image: 記事ページのメインビジュアル
  • thumbnail_image: 記事一覧のカードに表示するサムネイル
  • images: SNSシェア時のOGP画像(TwitterカードやOGタグで使用)

この分担は前の記事で書いたPageSpeed改善の取り組みとも関連しています。

少し管理は面倒になりましたが、快適にサイトが見れるようにやっていくには、これがいいと今のところ思ってます。

よかったこと

リポジトリを軽く保てるのが一番の効果でした。

  • 画像を差し替えてもGit履歴に古いファイルが積み上がりません。R2上で上書きすれば終わりです。
  • アイキャッチ・サムネイル・OGPと用途別の画像を整理して置きやすくなりました。

気をつけること

移行してよかった一方で、いくつか注意点もあります。

R2側のURL設計が後から変えにくい。 記事から参照しているURLを変えると、過去記事の画像が壊れます。最初からディレクトリ構造とファイル命名規則を決めておく必要があります。

画像を消すと記事の表示が壊れる。 GitHubのように「削除もバージョン管理する」という感覚はR2にはありません。不要になった画像でも、参照している記事がないか確認してから消す必要があります。

GitHubだけ見ても画像の実体がわからない。 リポジトリを見ても画像は見えません。バックアップや棚卸しは別途R2側で行う必要があります。

キャッシュと公開設定に注意する。 R2バケットのパブリックアクセス設定やCloudflareのキャッシュ設定を間違えると、画像が表示されなかったりキャッシュが意図せず残ったりします。

「R2に置けば解決」ではなく、運用ルールをセットで決めることが大事だと感じました。

今の運用ルール

実際にやっていることをまとめると、こんな感じです。

ディレクトリ構成: 年月で分けています。

uploads/
  2026/
    05/
      article-slug-eyecatch.webp
      article-slug-thumb.webp
      article-slug-og.jpg

ファイル命名規則: 記事のslugをベースに、用途をsuffixで区別します。

用途 ファイル名の例 形式の目安
記事ページ用アイキャッチ slug-eyecatch.webp WebP
一覧サムネイル用 slug-thumb.webp 軽量WebP
SNSカード用 slug-og.jpg 1200x630 JPEG

まとめ

HugoはGitHubで記事を管理しやすいですが、画像まで全部GitHubに入れる必要はありません。

「本文・テンプレート・設定はGitHub、画像はR2」という分担にしたことで、リポジトリが軽く保てるようになりました。ブログ運用では「どこに置けるか」より「後から重くならないか」を考えたほうが長続きすると思います。

画像が増えてから整理しようとすると、URL変更による過去記事の破損リスクが出てきます。早めに置き場所と命名ルールを決めておくのが一番楽です。