このブログの作り方 — Hugo、カスタムテーマ、Azureへの自動デプロイ
目次
このブログは完全に無料で動いています — 管理サーバーなし、データベースなし、ランタイムなし。新しいmarkdownファイルをGitHubにpushすると、30秒ほどでkhangnghiem.comにそのまま公開されます。SSHも、再起動も、月々のホスティング料金も不要です。
この記事では、スタック全体を解説します。Hugoとは何か・どう動くか、コンテンツの構成方法、ゼロから作ったカスタムテーマ、多言語対応(ベトナム語・英語・日本語)、そしてGitHub ActionsとAzure Static Web Appsを使った自動CI/CDパイプラインについて。
Hugoとは何か、なぜ選んだか
Hugoは静的サイトジェネレーターです。markdownファイルを入力として受け取り、純粋なHTML・CSS・JSを出力するプログラムです。サーバーサイドレンダリングも、データベースも、ランタイムの依存関係も不要です。結果は、どんなCDNやWebサーバーからでも配信できる静的ファイルを含む public/ フォルダです。
Hugoを選んだ理由は三つあります。
ビルドが速い。 Hugoは記事数に関係なく、数秒でサイト全体をビルドします。10記事以上あるこのブログでも500ms未満でビルドが完了します。Jekyll(Ruby)やNext.jsは大規模になると大幅に遅くなります。
JavaScriptランタイムが不要。 Node.jsも、npm installも、node_modulesも不要です。Hugoのインストールは単一のバイナリだけ — すぐに使えます。
ページバンドル。 Hugoでは各記事が独自のフォルダに入り、markdownと画像を一緒に管理できます。画像を別の場所に置いて絶対パスで参照するより、はるかに便利です。
Hugoの仕組み — markdownからWebサイトへ
content/ hugo.toml
post/ (設定)
java-threads/ │
index.md ──────────────────┤
main.jpg │
page/ │
about/index.md ──────────────┤
▼
themes/blog/ Hugo ビルドエンジン
layouts/ │
_default/ │ 1. 設定を読み込む
baseof.html ────────────────┤ 2. コンテンツツリーを走査
single.html ────────────────┤ 3. markdown → HTML に変換
list.html ──────────────────┤ 4. テンプレートを適用
assets/ │ 5. アセットを処理(CSS、JS)
css/main.css ──────────────────┤ 6. 出力を書き込む
js/main.js ────────────────────┘
│
▼
public/
post/
java-threads/
index.html ← 完成したHTML、配信準備完了
page/
about/index.html
index.html
index.json ← 検索インデックス
hugo --minify を実行すると、エンジンは:
hugo.tomlからベースURL、テーマ、ページネーションサイズなどの設定を読み込むcontent/フォルダ全体を走査し、フロントマター(+++の間のTOML)を解析する- GoldmarkレンダラーでmarkdownをHTMLに変換する
themes/blog/layouts/から適切なレイアウトテンプレートを見つけてレンダリングする- Hugo PipesでCSS/JSを処理する(ミニファイ、フィンガープリント)
- 全ての出力を
public/に書き込む
どのステップもインターネットやデータベースを必要としません。全ての処理がローカルで行われます。
コンテンツの構成
ページバンドル — 各記事はひとつのフォルダ
content/
├── post/
│ ├── java-threads-05-2026/
│ │ ├── index.vi.md ← ベトナム語のコンテンツ
│ │ ├── index.en.md ← 英語訳(存在する場合)
│ │ └── main.jpg ← カバー画像(同じフォルダ)
│ ├── database-optimization-java-06-2026/
│ │ └── index.vi.md
│ └── the-wind-rises-10-2025/
│ ├── index.vi.md
│ ├── main.jpg
│ └── gallery-1.jpg ← ギャラリーショートコードで使用する画像
└── page/
├── about/
│ ├── index.vi.md
│ ├── index.en.md
│ └── index.ja.md
├── archives/
│ ├── index.vi.md
│ ├── index.en.md
│ └── index.ja.md
└── search/
├── index.vi.md
├── index.en.md
└── index.ja.md
ページバンドルの利点:画像が記事と同じ場所にあり、 のような単純なファイル名で参照できます。Hugoはビルド時に正しいパスを解決します。ファイル名の言語サフィックス(index.vi.md、index.en.md、index.ja.md)により、複数の翻訳を1つのバンドルに置けます — どのファイルがどの言語に属するかをHugoが自動的に判断します。
フロントマター — 各記事のメタデータ
すべての記事は +++ の間のTOMLフロントマターで始まります:
+++
author = "Khang Nghiem"
title = "Javaのスレッドについて全て"
date = "2026-05-31"
description = "シニアエンジニアの視点からスレッドを解説..."
categories = ["Engineering"]
tags = ["java", "concurrency", "deep-dive"]
image = "main.jpg"
draft = false
+++
Hugoはフロントマターを使って:
- リストページで記事を
date順に並べる - タクソノミーページを生成する(
/categories/engineering/、/tags/java/) - SEO用に
descriptionを<meta>タグにレンダリングする imageからカバー画像を表示する
draft = true を使うと、記事を公開せずに書くことができます。Hugoは本番ビルド時にドラフトをスキップしますが、hugo server --buildDrafts ではローカルで確認できます。
Hugoのタクソノミー — カテゴリーとタグページを自動生成
categories = ["Engineering"] を持つ記事があると、Hugoは自動的に:
/categories/— 全カテゴリーの一覧ページを作成/categories/engineering/— Engineingカテゴリーの記事一覧ページを作成
追加の設定は不要です。themes/blog/layouts/categories/ に対応するレイアウトテンプレートを作成するだけです:
list.html—/categories/(タグクラウド)用term.html—/categories/<name>/(記事一覧)用
/tags/ も同様です。
ショートコード — カスタムHTMLでmarkdownを拡張
通常のmarkdownは、YouTubeの埋め込みや画像グリッドなどをサポートしていません。Hugoではショートコードを書くことができます — markdownの中で使えるカスタムテンプレートタグです。
YouTubeショートコード — プライバシーに配慮した埋め込み:
{{< youtube dQw4w9WgXcQ >}}
themes/blog/layouts/shortcodes/youtube.html のテンプレート:
<div class="video-wrapper">
<iframe
src="https://www.youtube-nocookie.com/embed/{{ .Get 0 }}"
loading="lazy"
allowfullscreen>
</iframe>
</div>
youtube.com の代わりに youtube-nocookie.com を使うことで、再生する前にユーザーを追跡しません。
ギャラリーショートコード — 画像グリッド:
{{< gallery cols="3" >}}



{{< /gallery >}}
Cytoscapeショートコード — 技術記事で使うインタラクティブなグラフ図:
{{< cytoscape height="420" >}}
{
"nodes": [
{ "id": "n1", "label": "Producer", "type": "producer" },
{ "id": "n2", "label": "Kafka Topic", "type": "topic" },
{ "id": "n3", "label": "Consumer", "type": "consumer" }
],
"edges": [
{ "source": "n1", "target": "n2" },
{ "source": "n2", "target": "n3" }
]
}
{{< /cytoscape >}}
Cytoscape.jsはこのショートコードを使うページでのみ読み込まれます — 他のページのパフォーマンスには影響しません。
カスタムテーマ — ゼロから作る
既存のテーマは使いませんでした。themes/blog/ 全体をゼロから書きました。
なぜ既存のテーマを使わないのか
既存のテーマはたいてい:重い(使わないJSが多い)、深くカスタマイズしにくい、多くの依存関係がある、という問題があります。ゼロから作ることで完全なコントロールを得られます — 不要なCSSも、何をしているかわからないJSも、何もありません。
テンプレートの構造
themes/blog/layouts/
├── _default/
│ ├── baseof.html ← 全ページ共通のシェル
│ ├── single.html ← 単一記事のレイアウト
│ ├── list.html ← 記事一覧のレイアウト(カテゴリー/タグ)
│ └── _markup/
│ ├── render-image.html ← フック: 画像を自動でWebPに変換
│ └── render-codeblock-mermaid.html
├── index.html ← ホームページ
├── 404.html
├── categories/
│ ├── list.html ← /categories/(タグクラウド)
│ └── term.html ← /categories/<name>/(記事一覧)
├── tags/
│ ├── list.html
│ └── term.html
├── page/
│ └── archives.html ← /page/archives/(検索 + ブラウズタブ)
├── partials/
│ ├── head.html ← メタタグ、フォントを含む <head>
│ ├── header.html ← ナビゲーション
│ ├── footer.html ← websitecarbonバッジ付きフッター
│ ├── post-card.html ← リストビュー用カードコンポーネント
│ ├── toc.html ← 目次
│ └── pagination.html
└── shortcodes/
├── youtube.html
├── gallery.html
└── cytoscape.html
baseof.html — 共通シェル
baseof.html は全ページが継承するルートテンプレートです。基本的な構造と、子テンプレートがオーバーライドできるブロックを定義します:
<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}">
<head>
{{- partial "head.html" . -}}
{{- block "head-extra" . }}{{- end }}
</head>
<body data-theme="light">
{{- partial "header.html" . -}}
<main>
{{- block "main" . }}{{- end }}
</main>
{{- partial "footer.html" . -}}
<script src="{{ .Site.BaseURL }}js/main.js" defer></script>
</body>
</html>
head-extra ブロックにより、single.html はカバー画像のプリロードリンクを <head> に挿入できます:
{{- define "head-extra" -}}
{{ with .Params.image }}
<link rel="preload" as="image"
href="{{ ($.Page.Resources.GetMatch .) | images.Process "1200x675 webp q85" | .RelPermalink }}"
fetchpriority="high">
{{ end }}
{{- end -}}
fetchpriority="high" とプリロードの組み合わせにより、カバー画像ができるだけ早く読み込まれ、LCP(Largest Contentful Paint)が改善されます。
レンダーフック — 画像の自動WebP変換
これはHugoであまり知られていない機能です:レンダーフック。Hugoがmarkdownの画像  に遭遇すると、デフォルトの <img> タグの代わりに _markup/render-image.html のテンプレートを使ってレンダリングします。
このテンプレートは自動的に:
- Hugo画像パイプラインで画像を処理 → WebP
- 2つのサイズ(660wと1320w)の
srcsetを作成 loading="lazy"とdecoding="async"を追加- レイアウトシフトを防ぐための明示的なwidth/heightを追加
{{- $img := .Page.Resources.GetMatch .Destination -}}
{{- if $img -}}
{{- $small := $img | images.Process "660x webp q85" -}}
{{- $large := $img | images.Process "1320x webp q85" -}}
<figure>
<img
src="{{ $small.RelPermalink }}"
srcset="{{ $small.RelPermalink }} 660w, {{ $large.RelPermalink }} 1320w"
sizes="(max-width: 720px) 660px, 1320px"
alt="{{ .Text }}"
width="{{ $small.Width }}"
height="{{ $small.Height }}"
loading="lazy"
decoding="async">
{{ with .Text }}<figcaption>{{ . }}</figcaption>{{ end }}
</figure>
{{- end -}}
つまり、普通にmarkdownを書くだけで、Hugoが画像の最適化を全て処理してくれます — Squooshも、手動のImageMagickも、WebPへの変換を覚えておく必要もありません。
CSS — カスタムプロパティを使った1ファイル
全てのスタイルは assets/css/main.css にあります。プリプロセッサーも、PostCSSも、フレームワークも不要 — テーマ管理のためのカスタムプロパティ(CSS変数)を使った純粋なCSSだけです。
/* ライトモードのトークン */
:root {
--bg: #FAFAFA;
--surface: #FFFFFF;
--fg: #111111;
--fg-2: #52525B;
--fg-3: #A1A1AA;
--accent: #7C3AED; /* バイオレット — リンク、アクティブ状態 */
--wide-w: 980px; /* コンテナ幅 */
--prose-w: 660px; /* 記事の読み幅 */
--font-serif: 'Newsreader', Georgia, serif;
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
}
/* ダークモード — 変わる部分だけオーバーライド */
[data-theme="dark"] {
--bg: #0F0F0F;
--surface: #1A1A1A;
--fg: #F4F4F5;
--fg-2: #A1A1AA;
--fg-3: #52525B;
}
ダークモードは <body> の data-theme="dark" をトグルすることで動作します。全コンポーネントがカスタムプロパティを使っているため、自動的に適応します。CSSルールを重複させる必要はありません。
FOUC(スタイルなしコンテンツの一瞬表示)を防ぐ:
<!-- <head> の中、CSSより前 -->
<script>
// このスクリプトは同期的に実行され、ブラウザが何かをレンダリングする前に動きます。
// localStorageから保存された設定を読み込み、すぐにテーマを設定します。
const saved = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (saved === 'dark' || (!saved && prefersDark)) {
document.documentElement.setAttribute('data-theme', 'dark');
}
</script>
このスクリプトがないと、ダークモードのユーザーはページ読み込み時に白いフラッシュを見ることになります — ブラウザが最初のフレームをレンダリングした後にCSSが読み込まれるためです。
JavaScript — フレームワークなし
assets/js/main.js はバニラJS、約300行で以下を処理します:
- テーマトグル — localStorageを読み書きし、
data-theme属性を設定 - モバイルナビゲーション — 小さい画面でメニューをトグル
- ビュートグル — ホームページでリスト/グリッドビューを切り替え、localStorageに保存
- スクロールトップボタン — スクロール後に表示
- コードコピーボタン — 全コードブロックにコピーボタンを挿入、クリップボードAPIを処理
- TOCトラッカー — スクロール中に目次で現在の見出しをハイライト
- プログレスベースのヘッダー — 読書進捗に応じてヘッダーのテキストが変化:「良い読書を。」 → 「もうすぐです。」 → 「お読みいただきありがとうございます。」
jQuery も、Alpine.js も、標準のWeb API以外は何も使っていません。HugoはJSファイルを defer で別ファイルとして配信し、パース中にメインスレッドを解放します。
多言語対応 — Hugo Multilingual Mode
このブログはHugoの組み込み多言語モードを使って、3つの言語(ベトナム語 /vi/、英語 /en/、日本語 /ja/)をサポートしています — プラグインも追加ライブラリも不要です。
hugo.toml の設定:
defaultContentLanguage = "vi"
defaultContentLanguageInSubdir = true # 全言語がURLプレフィックスを持つ
[languages]
[languages.vi]
languageCode = "vi"
languageName = "Tiếng Việt"
weight = 1
[languages.en]
languageCode = "en"
languageName = "English"
weight = 2
[languages.ja]
languageCode = "ja"
languageName = "日本語"
weight = 3
defaultContentLanguageInSubdir = true により、どの言語も / に「隠れる」ことがありません — 全コンテンツが明確なプレフィックスを持ちます。
UI文字列はテンプレートから i18n/vi.toml、i18n/en.toml、i18n/ja.toml に抽出されます。テンプレートではハードコードされたテキストの代わりに {{ i18n "key" }} を使います:
# i18n/ja.toml
[recent_posts]
other = "最近の記事"
[min_read]
other = "分で読めます"
言語スイッチャーはヘッダーにVI/EN/JAを表示します。Hugoの .AllTranslations を使って翻訳が存在するかを確認し、翻訳があればリンクを、なければグレーアウトで表示します:
{{ range slice "vi" "en" "ja" }}
{{ $match := index (where $.AllTranslations "Lang" .) 0 }}
{{ if eq . $.Site.Language.Lang }}
<span class="lang-btn is-active">{{ upper . }}</span>
{{ else if $match }}
<a href="{{ $match.RelPermalink }}" class="lang-btn">{{ upper . }}</a>
{{ else }}
<span class="lang-btn is-disabled">{{ upper . }}</span>
{{ end }}
{{ end }}
記事に翻訳を追加するには、index.vi.md と同じフォルダに index.en.md を作成するだけ — スイッチャーが自動的に認識してリンクを有効にします。
検索 — 完全クライアントサイド
サーバーも、Algoliaも、ElasticSearchも不要です。検索はこのように動作します:
- Hugoが
/vi/page/search/index.json、/en/page/search/index.json、/ja/page/search/index.jsonを生成 — 各言語がその言語の記事のみを含む独自のJSONファイルを持つ - ユーザーが検索ページを開くと、
search.jsがHugoによって事前にレンダリングされた(言語対応の)data-search-url属性からURLを取得してフェッチする - タイトル、タグ、カテゴリーによるフィルタリングは完全にブラウザ内で行われる
// search.js — コアロジック
async function initSearch() {
const root = document.getElementById('search-root');
const searchUrl = root?.dataset.searchUrl; // HugoがURLを言語ごとに正しくレンダリング
const response = await fetch(searchUrl);
const posts = await response.json();
searchInput.addEventListener('input', () => {
const query = searchInput.value.toLowerCase().trim();
const results = posts.filter(post =>
post.title.toLowerCase().includes(query) ||
post.tags?.some(t => t.toLowerCase().includes(query)) ||
post.categories?.some(c => c.toLowerCase().includes(query))
);
renderResults(results);
});
}
現在の記事数では、検索JSONは10KB未満でブラウザでのフィルタリングは即座に完了します。数千の記事があれば別の解決策(Pagefindなど)が必要ですが、このスケールでは不要です。
自動デプロイパイプライン
全体の流れ
記事を書く(markdown)
│
▼
git push origin main
│
▼
GitHub Actions が起動
│
┌─────▼──────────────┐
│ 1. コードをチェック│
│ アウト │
│ 2. Hugo をインスト │
│ ール │
│ 0.152.2 extended│
│ 3. hugo --minify │
│ (ビルド → public/)│
│ 4. public/ を │
│ Azure にアップロ│
│ ード │
└─────────────────────┘
│
▼
Azure Static Web Apps
(グローバルCDN配信)
│
▼
khangnghiem.com が公開
(pushから30〜60秒)
GitHub Actionsワークフロー
ファイル .github/workflows/azure-static-web-apps-kind-field-012958600.yml:
name: Azure Static Web Apps CI/CD
on:
push:
branches:
- main # mainへのpushごとにトリガー
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main # プルリクエストのプレビューデプロイ
jobs:
build_and_deploy_job:
runs-on: ubuntu-latest
steps:
# 1. リポジトリをクローン
- uses: actions/checkout@v3
# 2. 正しいバージョンのHugoをインストール
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.152.2'
extended: true # 画像処理のためにextendedバージョンが必要
# 3. サイトをビルド
- name: Build Hugo site
run: hugo --minify # HTML、CSS、JS出力をミニファイ
# 4. public/ をAzureにアップロード
- name: Deploy to Azure Static Web Apps
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
action: "upload"
app_location: "public" # ビルド出力フォルダ
skip_app_build: true # 重要:AzureがOrxで再ビルドするのを防ぐ
skip_api_build: true
skip_app_build: true がなぜ必要か?
Azure Static Web AppsにはOrxという独自のビルドエンジンがあります。このフラグがないと、OrxはフレームワークをDetectして再ビルドしようとします — しかしHugoを知らないため、失敗するか間違った出力を生成します。GitHub ActionsでHugoをビルドしてから public/ フォルダをアップロードすることで、Azureはファイルを配信するだけで、追加のビルドは必要ありません。
プルリクエストのプレビューデプロイ:
各プルリクエストに自動的に専用のプレビューURL(例:https://kind-field-012958600-123.westus2.azurestaticapps.net)が割り当てられます。公開前に新しい記事をレビューするのに便利です。PRがマージされると、プレビューデプロイは自動的に削除されます。
Hugoバージョンの固定:
hugo-version: '0.152.2' によりビルドは常に同じバージョンを使用します — GitHubランナーのHugoバージョンがローカルと異なる場合の「自分のマシンでは動く」問題を防ぎます。
Azure Static Web Apps — なぜ選んだか
静的サイトは無料。 Azure Static Web Appsの無料枠には以下が含まれます:
- 静的ファイルのホスティング(HTML、CSS、JS、画像)
- グローバルCDN配信
- カスタムドメインで自動HTTPS
- プルリクエストのプレビュー環境
カスタムドメインと自動HTTPS。 khangnghiem.com のDNSをAzureに向けると、AzureがLet’s Encryptを通じてSSL証明書を自動的に発行・更新します — 追加設定は不要です。
グローバルCDN。 ファイルは世界中の多くのAzureエッジロケーションに配布されます。訪問者は米国へのラウンドトリップを待つのではなく、最寄りのエッジからファイルを取得します。
セキュリティヘッダーとURLリダイレクト — staticwebapp.config.json
AzureはこのファイルをHTTPヘッダーの注入とルーティング処理に使います。セキュリティヘッダーに加え、多言語対応前の古いURLを正しい新しいURLに転送するリダイレクトルールも含まれています:
{
"routes": [
{ "route": "/", "redirect": "/vi/", "statusCode": 301 },
{ "route": "/post/*", "redirect": "/vi/post/*", "statusCode": 301 },
{ "route": "/page/*", "redirect": "/vi/page/*", "statusCode": 301 },
{ "route": "/categories/*","redirect": "/vi/categories/*", "statusCode": 301 },
{ "route": "/tags/*", "redirect": "/vi/tags/*", "statusCode": 301 }
],
"globalHeaders": {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "SAMEORIGIN",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Content-Security-Policy": "
default-src 'self';
script-src 'self' 'unsafe-inline'
https://www.googletagmanager.com
https://unpkg.com
https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline'
https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
frame-src
https://www.youtube-nocookie.com
https://www.youtube.com;
connect-src 'self'
https://www.google-analytics.com
https://analytics.google.com
https://api.websitecarbon.com
"
}
}
CSP(コンテンツセキュリティポリシー)は、ホワイトリストに登録されたドメインからのみスクリプト・スタイル・フォントの読み込みを許可することでXSSを防ぎます。外部ドメインは全て明示的に宣言する必要があります — Google Analytics、Google Fonts、jsDelivrのCytoscape、unpkgのwebsitecarbonバッジ。
日々の執筆ワークフロー
新しい記事を書きたいとき、流れはこうなります:
# 1. Hugo CLIで新しい記事をスキャフォールド
hugo new post/post-name-06-2026/index.vi.md
# HugoがフロントマターテンプレートでファイルToを作成
# 翻訳したいときは同じフォルダにindex.en.mdやindex.ja.mdを追加
# 2. ライブリロードでdevサーバーを起動
hugo server --buildDrafts
# → localhost:1313
# ファイルを保存 → ブラウザが即座に更新
# 3. 記事が完成したら、draft = false にしてコミット
git add content/post/post-name-06-2026/
git commit -m "記事Xを追加"
git push
# 4. GitHub Actionsが自動実行し、30〜60秒後に公開
HugoのDevサーバーは非常に高速です — markdownの変更が100ms未満で再ビルドされ、ブラウザが更新されます。JavaScriptバンドラーもTypeScriptコンパイルもないため、フィードバックループはほぼ即座です。
このスタックにないもの
CMSがない。 WordPress も、Contentful も、Strapi もありません。コンテンツはgitリポジトリ内のプレーンテキストのmarkdownファイルです。メリット:オフラインでの執筆、バージョン管理、全変更の差分履歴。デメリット:WYSIWYGエディターなし、git/markdownを知らない人向けのGUIなし。
サーバーサイドレンダリングがない。 全てのHTMLはビルド時に生成されます。Express.jsも、Spring Bootも、リクエスト処理もありません。メリット:ランタイム障害なし、サーバーコストなし、ほぼゼロのTTFB(CDNレイテンシーのみ)。デメリット:動的コンテンツなし(コメント、ユーザー認証、パーソナライゼーション)。
データベースがない。 コンテンツはmarkdownファイルに存在します。PostgreSQLも、MongoDBも、Redisもありません。gitリポジトリ以外にバックアップするものは何もありません。
CIテストスイートがない。 ビルド失敗 = デプロイ失敗 — 個人ブログにはこれで十分です。チームのプロジェクトにはもっと必要です。
まとめ
このスタックはユースケースに完全に合っています:markdownで書く個人技術ブログで、動的な機能が不要。ホスティングコスト:$0。メンテナンス時間:ほぼゼロ(Hugoバージョン以外の依存関係の更新なし)。デプロイ:初期設定後は完全自動。
同じことをやりたい方へ、セットアップの順番:
- Hugoをインストール — 1つのバイナリだけ、他は何も不要
- GitHubリポジトリを作成 — コードをpush
- Azure Static Web Appsリソースを作成 — 無料枠、GitHubリポジトリと連携
- AzureがワークフローファイルToを自動生成 — アップロード前にHugoをビルドするよう編集
- カスタムドメインを向ける — AzureがSSLを処理
何もない状態からWebサイトが公開されるまで、約30〜60分です。