Hugoで作ったブログをCloudflare Pagesにデプロイするまでにやったこと

Hugoで作ったブログをCloudflare Pagesにデプロイするまでにやったこと

2026.01.11

はじめに

皆様こんにちは、あかいけです。

最近 Hugo でブログを作ったらいい感じだったので、共有します。
皆さんも情報発信の一手段として始めてみてはいかがでしょうか。

今回作ったもの

リクガメとIT技術に関するブログを作りました。
以下リンクからアクセスできます。

https://tortoise-tech-blog.lamaglama39.dev/

やりたいことと実現方法

まずブログを作るにあたって、以下の要件と実現方法を整理しました。
色々書いていますが、個人的に一番大事なのは運用コストかなと思います。

やりたいこと 実現方法 理由
カスタムドメインを使いたい Cloudflare ドメイン管理とDNS設定が一箇所で完結できる
できるだけ安く運用したい Cloudflare Pages + R2 無料枠が充実しており、個人ブログ規模なら無料で運用可能の見込み
レスポンスを早くしたい Cloudflare 世界中にエッジサーバーがあり、CDN配信で高速できそう
ビルドを早くしたい Hugo 1万ページでも10秒以内にビルド可能(らしい)
簡単にデプロイしたい GitHub + Cloudflare Pages pushするだけで自動デプロイ、複雑なCI/CD設定が不要
マークダウンで記事を書きたい Hugo 標準機能でマークダウンをHTMLに変換できる

なぜ Hugo + Cloudflare なのか

ブログを作るのはいいとして、
なぜHugoとCloudflareという組み合わせなのでしょうか?

SSGを使いたいだけならNext.jsでもGatsbyでもDocusaurusでもAstroでもいい気がしますし、デプロイ先としてもVercelやAWS、それこそGitHub Pagesでも良さそうな気がします。

もちろん他の選択肢でも問題ないのですが、今回は以下の理由からこの組み合わせを選びました。

Hugoを使う理由

https://github.com/gohugoio/hugo

ビルドが早い

HugoはGo言語で書かれており、ビルド速度が圧倒的に速いらしく、公式ドキュメントでも以下のように記載されています。
(今回他のフレームワークと比較はしていませんが、検索すると速度を比較した記事がいくつか出てきます)

Hugo renders a complete site in seconds, often less.

https://gohugo.io/about/introduction/

執筆中にファイルを保存するたびにビルドが走りますが、Hugoなら一瞬で反映されるのでストレスがありません。

シンプル

Hugoは静的サイトジェネレーター(SSG)に特化しています。
SSGではサーバーサイドの処理やデータベースは必要なく、生成されるのは純粋な静的HTMLファイルのみです。
そして今回のような静的コンテンツがメインのブログならSSGで十分と判断しました。

逆に言えばSPAやSSRやISRなどJavaScriptをバリバリ使ったりサーバーサイドでの処理をしたい場合は、
他のフレームワークを使いましょう。

テンプレートが豊富

コミュニティで大量のテーマが公開されており、デザインセンスがなくてもいい感じのブログが作れます。

https://themes.gohugo.io/

今回使用したterminalテーマもその一つです。

Cloudflareを使う理由

無料枠が充実している

Cloudflare Pagesの無料枠は個人ブログには十分すぎるスペックがあります。
公式ドキュメントの抜粋です。

You can build up to 500 times per month on the Free plan.

Cloudflare Pages sites can contain up to 20,000 files.

https://developers.cloudflare.com/pages/platform/limits/

項目 無料枠
ビルド回数 500回/月
同時ビルド 1
帯域幅 無制限

特に帯域幅が無制限なのが嬉しいポイントです。
もしアクセスが急増しても追加料金を気にする必要がありません。

またCloudflare R2も同様に以下の無料枠があります。
10GB/月は無料であり、これも個人ブログであれば十分でしょう。

https://www.cloudflare.com/ja-jp/developer-platform/products/r2/

全体構成

今回構築するブログの全体構成は以下のとおりです。

  • 開発環境: Dev Containers (VSCode)
  • SSG: Hugo
  • ホスティング: Cloudflare Pages
  • 画像配信: Cloudflare R2 + カスタムドメイン
  • ソースコード管理: GitHub

GitHubにpushするとCloudflare Pagesが自動でビルド・デプロイを行い、画像はR2から配信される構成です。

構築していく

それでは実際に構築していきましょう。

開発環境を用意する

Hugoの開発環境を用意します。
インストール方法はいくつかあり、OSごとのインストール方法は以下ドキュメントに記載されています。

https://gohugo.io/installation/

今回はDev Containersを使います。

https://github.com/devcontainers/features/pkgs/container/features%2Fhugo

Dev Containersを使うことで、ローカル環境を汚さずにHugoの開発環境を構築できます。

まずは以下のような設定ファイルを作っておきます。

{
  "name": "tortoise-tech-blog-devcontainer",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "features": {
    "ghcr.io/devcontainers/features/go:1": {
      "version": "1.18"
    },
    "ghcr.io/devcontainers/features/docker-in-docker:2.13.0": {
      "version": "latest",
      "moby": true
    }
  },
  "forwardPorts": [1313],
  "postCreateCommand": "hugo version"
}

次にVSCodeの左下にあるリモート接続アイコンをクリックします。

Screenshot from 2026-01-10 17-58-14

表示されるメニューから「コンテナーで再度開く」を選択します。新しいVSCodeウィンドウが開きます。

Screenshot from 2026-01-10 17-58-39

初回はDockerイメージのダウンロードが必要なので、通信環境によっては起動まで数分程度時間がかかります。
以下のような表示が出たらセットアップ完了です。

Screenshot from 2026-01-10 18-39-27

ターミナルを開くと、コンテナ内で起動していることが確認できます。
ユーザー名はデフォルトだとvscodeになります。

Screenshot from 2026-01-10 18-46-04

Hugo 初期セットアップ

プロジェクト作成

以下のコマンドでHugoプロジェクトを作成します。

hugo new site blog;
cd blog;
git init;
git branch -m main;

デフォルトのディレクトリ構成

プロジェクトを作成すると、以下のようなディレクトリ構成が生成されます。

.
├── archetypes
│   └── default.md
├── assets
├── content
├── data
├── hugo.toml
├── i18n
├── layouts
├── static
└── themes

各ディレクトリ・ファイルの役割は以下のとおりです。

ディレクトリ/ファイル 説明
archetypes/ 記事のテンプレートを格納。hugo newで記事を作成する際に使用される
assets/ SCSSやTypeScriptなど、ビルド時に処理されるファイルを格納
content/ ブログ記事やページのマークダウンファイルを格納
data/ サイト全体で使用するデータファイル(JSON, YAML, TOML)を格納
hugo.toml Hugoの設定ファイル
i18n/ 多言語対応用の翻訳ファイルを格納
layouts/ カスタムレイアウトテンプレートを格納
static/ 画像やCSS、JavaScriptなど、そのまま配信される静的ファイルを格納
themes/ テーマを格納

テーマ追加

Hugoはコミュニティで様々なテーマが公開されています。

https://themes.gohugo.io/

今回は以下のterminalテーマを使ってみます。
ターミナル感が中々良くて、見ていて心が安らぐ気がします。

https://themes.gohugo.io/themes/hugo-theme-terminal/

テーマの追加方法はgit cloneする方法とサブモジュールとして追加する方法があります。
既存のリポジトリに追加したいので、今回はサブモジュールとして利用します。

テーマ追加
git submodule add -f https://github.com/panr/hugo-theme-terminal.git themes/terminal

Hugoの設定ファイルに theme を追加します。
これで追加したテーマが反映されます。

hugo.toml
baseURL = 'https://example.org/'
languageCode = 'en-us'
title = 'My New Hugo Site'
theme = 'terminal'

記事の作成

Hugoではhugo new contentコマンドで記事を作成できます。
このコマンドを実行すると、archetypes/にあるテンプレートを元に新しいマークダウンファイルが生成されます。

https://gohugo.io/commands/hugo_new_content/

hugo new content content/posts/post-1.md;
hugo new content content/posts/post-2.md;
hugo new content content/posts/post-3.md;

コマンドを実行すると、以下のようなファイルが作成されます。
ファイルの先頭にある+++で囲まれた部分は「Front Matter」と呼ばれ、記事のメタデータを定義します。

content/posts/post-1.md
+++
title = "Post 1"
date = "2026-01-10T10:18:53Z"
#dateFormat = "2006-01-02" # This value can be configured for per-post date formatting
author = ""
authorTwitter = "" #do not include @
cover = ""
tags = ["", ""]
keywords = ["", ""]
description = ""
showFullContent = false
readingTime = false
hideComments = false
+++

主要なFront Matterフィールドの説明は以下のとおりです。

フィールド 説明
title 記事のタイトル
date 記事の作成日時(ISO 8601形式)
author 著者名
cover カバー画像のパス
tags 記事に付けるタグ(配列)
description 記事の説明文(メタタグに使用)
draft trueにすると下書き状態となり、本番ビルドから除外される

Front Matterの詳細は公式ドキュメントを参照してください。

https://gohugo.io/content-management/front-matter/

ローカルサーバー立ち上げ

Hugoには開発用のローカルサーバーが組み込まれています。
以下のコマンドで起動できます。

hugo server -D
オプション 説明
-D / --buildDrafts 下書き状態(draft: true)の記事も表示する
-F / --buildFuture 公開日が未来の記事も表示する

https://gohugo.io/commands/hugo_server/

サーバーが起動したら、ブラウザで以下のURLにアクセスします。

http://localhost:1313/

Hugoの開発サーバーにはLive Reload機能があり、ファイルを保存すると自動的にブラウザがリロードされます。
これにより、変更内容をリアルタイムで確認しながら記事を執筆できます。嬉しいですね。

Screenshot from 2026-01-10 19-21-10

カスタマイズ

ここからはterminalテーマをベースにHugoの設定をカスタマイズしていきます。

テーマカラーの変更

terminalテーマでは、公式がカラーパレットジェネレーターを提供しています。
このツールを使うと、好みの配色でCSSファイルを簡単に生成できます。

https://panr.github.io/terminal-css/

生成したCSSファイルと、サイトのアイコンなどはstatic/ディレクトリに配置します。
static/に配置したファイルは自動的にビルド時に含まれます。

/static/terminal.css    # テーマのカスタムCSS
/static/favicon.png     # ブラウザタブに表示されるアイコン
/static/og-image.png    # SNSでシェアされた際に表示される画像
ファイル 説明
terminal.css テーマカラーを上書きするカスタムCSS
favicon.png ブラウザのタブやブックマークに表示されるアイコン
og-image.png Open Graphプロトコル用の画像。SNSでリンクをシェアした際のプレビューに使用される

hugo 設定ファイル

Hugoの設定はTOML、YAML、JSONのいずれかの形式で記述できます。
デフォルトではhugo.tomlが使用されます。

設定項目が多いですが、ほとんどはテーマの公式ドキュメントの「How to configure」を参考にしています。

https://github.com/panr/hugo-theme-terminal

以下に今回作成した設定ファイルは以下の通りです。

hugo.toml
baseurl = "https://your-domain.com/"
languageCode = "ja"
theme = "terminal"
pagination.pagerSize = 5
copyright = "© 2026 リクガメてっく。"
rssLimit = 20

[outputs]
  home = ["HTML", "RSS"]
  section = ["HTML", "RSS"]
  taxonomy = ["HTML", "RSS"]

[markup.highlight]
  noClasses = false

[params]
  contentTypeName = "posts"
  showMenuItems = 3
  showLanguageSelector = false
  fullWidthTheme = false
  centerTheme = true
  autoCover = true
  showLastUpdated = false
  dateFormat = "2006年01月02日 15:04"

[params.twitter]
  creator = "lamaglama39"
  site = "lamaglama39"

[languages]
  [languages.ja]
    languageName = "日本語"
    title = "リクガメてっく。"

    [languages.ja.params]
      subtitle = "癒やしのリクガメとIT技術のブログです。"
      readMore = "続きを読む"
      readOtherPosts = "他の記事を読む"
      newerPosts = "新しい記事"
      olderPosts = "古い記事"
      missingContentMessage = "ページが見つかりません..."
      missingBackButtonLabel = "ホームに戻る"
      minuteReadingTime = "分で読めます"
      words = "文字"

      [languages.ja.params.logo]
        logoText = "リクガメてっく。"
        logoHomeLink = "/"

      [languages.ja.menu]
        [[languages.ja.menu.main]]
          identifier = "about"
          name = "このブログについて"
          url = "/about"
          weight = 1
        [[languages.ja.menu.main]]
          identifier = "tags"
          name = "タグ一覧"
          url = "/tags"
          weight = 2
        [[languages.ja.menu.main]]
          identifier = "rss"
          name = "RSS"
          url = "/index.xml"
          weight = 3
  • 設定セクションの説明
セクション 説明
トップレベル設定 baseurl, languageCode, themeなどサイト全体の基本設定。baseurlはデプロイ先のURLに合わせて設定する
pagination.pagerSize 1ページあたりの記事表示数
rssLimit RSSフィードに含める記事の最大数
[outputs] 各ページタイプで生成する出力形式を指定。HTMLとRSSを生成する設定
[markup.highlight] シンタックスハイライトの設定。noClasses = falseでCSSクラスベースのハイライトを使用
[params] テーマ固有のパラメータ。terminalテーマのレイアウトや表示オプションを設定
[params.twitter] TwitterカードのメタタグにTwitterアカウント情報を埋め込む設定
[languages] 多言語対応の設定。日本語のラベルやメニュー項目をカスタマイズ
  • params セクションの詳細

[params]セクションはテーマ固有の設定を行えます。
terminalテーマでは以下の項目が設定可能です。

パラメータ 説明
contentTypeName コンテンツのデフォルトタイプ名
showMenuItems ヘッダーに表示するメニュー項目数
centerTheme コンテンツを中央揃えにするかどうか
autoCover 記事のカバー画像を自動検出するかどうか
dateFormat 日付の表示形式(Go言語の時刻フォーマット形式で指定)

その他、設定値に関する詳細は公式ドキュメントを参照してください。

https://gohugo.io/configuration/all/

これらの設定を反映すると以下の見た目になりました。
カスタマイズといっても大した変更はなく、テーマカラーとメニューをいくつか足したぐらいですね。

Screenshot from 2026-01-10 23-42-40

Cloudflareへのデプロイ

次にCloudflareへのデプロイをしていきます。
まずはソースコードを管理するGitHubリポジトリを作成します。

GitHubリポジトリ作成

GitHubで新規リポジトリを作成します。
リポジトリ名は任意ですが、ブログ名に合わせておくとわかりやすいです。

Screenshot from 2026-01-10 23-46-50

pushする前に、.gitignoreを作成して、
ビルド成果物などをコミット対象から除外しておきます。

.gitignore
# Generated files by hugo
**/public/
**/resources/_gen/
**/assets/jsconfig.json
**/hugo_stats.json

# Executable may be added to repository
**/hugo.exe
**/hugo.darwin
**/hugo.linux

# Temporary lock file while building
**/.hugo_build.lock

リポジトリにpushしておきます。

git remote add origin git@github.com:Lamaglama39/tortoise-tech-blog.git
git branch -M main
git push -u origin main

Cloudflare Pages作成

次にCloudflare Pagesを作成します。
Hugoのビルド設定については公式ドキュメントに詳しく記載されています。

https://developers.cloudflare.com/pages/framework-guides/deploy-a-hugo-site/

Cloudflareのダッシュボードから「Workers & Pages」を選択し、「アプリケーションを作成する」をクリックします。

Screenshot from 2026-01-10 23-53-42

ちょっと分かりづらいんですが、
画面下の Looking to deploy Pages? の「Get started」をクリックします。

Screenshot from 2026-01-11 00-46-11

「既存の Git リポジトリをインポートする」を選択します。

Screenshot from 2026-01-11 00-46-38

先ほど作成したリポジトリを選択します。

Screenshot from 2026-01-11 00-47-42

ビルド設定を行います。
フレームワークプリセットで「Hugo」を選択すると、ビルドコマンドと出力ディレクトリが自動で設定されます。
設定が完了したら「保存してデプロイする」をクリックします。

Screenshot from 2026-01-11 00-48-47

しばらくすると、以下のようにデプロイが完了します。

Screenshot from 2026-01-11 00-51-27

デフォルトではドメイン名は「プロジェクト名.pages.dev」となります。
ドメインにアクセスして、正常に表示されることを確認しましょう。

Screenshot from 2026-01-11 00-57-18

カスタムドメインの設定

次にカスタムドメインを設定します。
Cloudflare Pagesの設定画面から「Custom domains」タブを開きます。

Screenshot from 2026-01-11 00-59-09

使いたいドメイン名を入力します。
今回は取得しているドメインのサブドメインで設定します。

Screenshot from 2026-01-11 01-00-25

DNSレコードの設定内容が表示されるので、
問題なければ「ドメインをアクティブにする」をクリックします。

Screenshot from 2026-01-11 01-00-47

CloudflareでDNSを管理している場合、DNSレコードが自動的に追加されます。
数分後には以下のようにアクティブになりました。

Screenshot from 2026-01-11 01-04-09

ただしDNSレコードがグローバルに反映されるまでには時間がかかります。
そのため反映されるまでは、設定したカスタムドメインを検索しても以下のようなエラーが出ます。
また今回の場合は設定したカスタムドメインでアクセスできるようになるまで、20分程度かかりました。

Screenshot from 2026-01-11 01-15-41

カスタムドメインを設定したら、次にHugoの設定ファイルでベースURLを修正する必要があります。
また末尾にスラッシュ(/)が必要な点に注意が必要です。

hugo.toml
baseurl = "https://tortoise-tech-blog.lamaglama39.dev/"

画像ファイルはどこで保存する?

さて、記事に乗せる画像ファイルですが、
デフォルトでは他のフレームワークなどと同様に ./static 配下に配置します。
また記事からの参照方法は以下の通りです。

content/posts/post-1.md
### Markdown 記法
![macaroni](/about/macaroni.jpg)

### Shortcode 記法
{{< figure src="/about/macaroni.jpg" title="macaroni" width="50%" height="50%" >}}

![[Screenshot from 2026-01-11 01-23-14.png]]

しかしこの方法には一つ問題点があります。
それは Cloudflare Pages のビルド後に含められるファイル数に上限ある ことです。

Pages uploads each file on your site to Cloudflare's globally distributed network to deliver a low latency experience to every user that visits your site. Cloudflare Pages sites can contain up to 20,000 files.

https://developers.cloudflare.com/pages/platform/limits/#files

2万ファイルなのでよほどハードにブログを書かない限りは問題ないと思いますが、記事数と画像ファイル数を合わせると意外と上限に触れそうでちょっと怖いです。
また、画像ファイルをリポジトリに含めると、リポジトリサイズが肥大化してしまう問題もあります。

この対策として、Cloudflare Pages外に画像ファイルを保存して、ブログ内でそれを参照する方法があります。
いくつかの方法がありますが、今回はシンプルにR2 + カスタムドメインで配信する方法を採用します。

  • Cloudflare Images
  • R2 + カスタムドメイン
  • R2 + Workers

https://developers.cloudflare.com/pages/tutorials/use-r2-as-static-asset-storage-for-pages/

またR2のパブリック配信方法には以下の2つがあります。

  • カスタムドメイン: 独自ドメインで配信(推奨)
  • パブリック開発URL: Cloudflareが提供するURLで配信

今回はカスタムドメインを使用します。
まずR2バケットを作成します。

Screenshot from 2026-01-11 01-47-20

バケット名を入力して作成します。

Screenshot from 2026-01-11 01-48-02
バケットが作成されたら、画像ファイルをアップロードします。

Screenshot from 2026-01-11 01-52-32

次にR2バケットにカスタムドメインを設定します。
バケットの設定画面から「設定」を開いて、カスタムドメインの「追加」をクリックします。

Screenshot from 2026-01-11 01-53-18

画像配信用のサブドメインを入力します。

Screenshot from 2026-01-11 01-54-46

自動設定されるDNSレコードが表示されるので、問題がなければカスタムドメインを設定します。

Screenshot from 2026-01-11 01-55-13

設定が完了すると、カスタムドメイン経由でR2の画像にアクセスできるようになります。

Screenshot from 2026-01-11 01-58-15

試しにアップロードした画像は、以下のURLでアクセスできるようになっています。

https://image.tortoise-tech-blog.lamaglama39.dev/macaroni.jpg

記事ではそのURLを参照すればOKです。

content/posts/post-1.md
### Markdown 記法
![macaroni](https://image.tortoise-tech-blog.lamaglama39.dev/macaroni.jpg)

### Shortcode 記法
{{< figure src="https://image.tortoise-tech-blog.lamaglama39.dev/macaroni.jpg" title="macaroni" width="50%" height="50%" >}}

CORSについて

HugoはSSGなので、生成されるのは純粋なHTMLファイルのみです。
JavaScriptからR2の画像を動的に取得するわけではないため、CORS設定は不要です。

もしJavaScriptでR2のリソースにアクセスする必要がある場合は、CORS設定を追加する必要があります。

https://developers.cloudflare.com/r2/buckets/cors/#use-cors-with-a-custom-domain

さいごに

以上、Hugo + Cloudflare Pagesでブログを構築するまでにやったことでした。
今回の構成のポイントをまとめると以下のとおりです。

  • Hugo: ビルドが高速でシンプル。マークダウンで記事を書ける
  • Cloudflare Pages: 無料枠が充実。帯域幅無制限でGitHub連携も簡単
  • Cloudflare R2: 画像を外部に配置することでPagesのファイル数制限の対象外にできる

この構成なら、よほどハードにブログを書かない限り無料で運用できます。
個人ブログを始めてみたいけどコストが気になる…という方にはピッタリの選択肢だと思います。

ブログを書くことで自分の学びを整理できますし、きっと同じ問題で困っている誰かの助けになります。
皆さんも心当たりはありませんか?エラーで困っていたとき、検索結果に現れた謎の個人ブログに救われた経験が…。

次は皆さんが誰かを救う番です、ぜひ個人ブログを始めてみてはいかがでしょうか。

この記事をシェアする

FacebookHatena blogX

関連記事