どうも、マイルドインベスターです。 備忘録がてら、WordPressからHugo + Cloudflare Pagesへ移行するための全タスクを、実際の経験(wp2hugoツールとPaperMod採用)に基づいて整理しました。
1. ツール・環境の準備(Windows 11)
まずは、開発環境と外部サービスの準備を整えます。
- Hugoのインストール: Windows用パッケージマネージャー
Winget(標準搭載)を使い、コマンドプロンプトやPowerShellからインストール。- コマンド:
winget install Hugo.Hugo.Extended
- コマンド:
- Gitのインストール: サイトのソースコードの変更履歴管理とGitHubへの送信のためにインストール。
- AntigravityまたはVSCodeのインストール: Hugoテーマのカスタマイズ、記事の執筆や設定ファイルの編集に最適なエディタ。
- GitHubアカウントの作成: サイトのソースコードを保存・管理する場所を確保。
- Googleフォームの作成: お問い合わせフォームを作成し、完了画面のURLや埋め込みコードを控えておく。
2. WordPressデータの準備(移行元)
既存のWordPressからデータを抽出します。
- XMLデータのエクスポート: WordPressの管理画面(ツール > エクスポート)を使い、全記事データをXML形式でダウンロードする。
3. Hugoサイトの構築(wp2hugoによる移行)
Windows PC上で、移行ツールを活用してHugoサイトをローカルで確認します。
- wp2hugoによる一括変換: wp2hugo を実行し、WordPressのXMLからMarkdown形式へ一括変換。この際、定番テーマである PaperMod が自動的に設定されます。
- 設定ファイル(hugo.yaml)の編集: サイト名、URL構造(Permalinks)を自分の好みに変更する。
- ローカルプレビュー:
hugo serverを実行し、ブラウザ(localhost:1313)で表示を確認する。
4. 生成AI(Antigravity/Gemini)による徹底カスタマイズ
今回の移行で最も活躍したのは、AIエージェントのAntigravityとGemini 3.0 Flashです。特にAntigravityには、CSSの細かい微調整や複雑なショートコードのロジック実装を「こうしたい」という言葉だけで依頼し、自律的にコードを生成・修正してもらいました。 カスタマイズした内容をご紹介します。
4.1. 基本設定:Google Analytics と Google AdSense の埋め込み
PaperModでは、layouts/partials/extend_head.html を作成(または編集)することで、<head> タグ内に独自のコードを挿入できます。
- ads.txtの配置: 旧サイトから
ads.txtをダウンロードして、static/フォルダ直下に配置。
Google AdSense
hugo.yaml に定義した googleAdSense パラメータを参照するようにしています。
{{- if site.Params.googleAdSense -}}
<script async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client={{ site.Params.googleAdSense }}"
crossorigin="anonymous"></script>
{{- end -}}
Google Analytics
こちらは layouts/partials/google_analytics.html を作成し、同様に hugo.yaml からIDを取得します。
{{- if site.Params.googleAnalytics -}}
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.Params.googleAnalytics }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', '{{ site.Params.googleAnalytics }}');
</script>
{{- end -}}
4.2. お問い合わせページの作成(Googleフォーム)
事前にGoogleフォームで問い合わせフォームを作成し、その埋め込みコードをコピーしておく。
/content/pages/contact-us/index.md を作成し、Googleフォームの埋め込みコードを記述する。
---
title: お問い合わせ
url: /contact-us/
---
お問い合わせは下記のフォームに必要事項を記入して送信してください。
<iframe src="https://docs.google.com/forms/d/..." width="640" height="800" ...>読み込んでいます…</iframe>
4.3. カスタムショートコードの作成
記事内で使い回せる便利なショートコードをいくつか作成しました。ロジック(HTML/Hugoテンプレート)とレイアウト(CSS)の両方をセットで実装しています。
Amazonアフィリエイトリンク
ASINとタイトルを指定するだけで画像を自動取得し、整形するショートコードです。
layouts/shortcodes/amazon.html
{{- $tag := site.Params.amazonJpAffiliate -}}
{{- $asin := .Get "asin" -}}
{{- $title := .Get "title" -}}
<div class="amazon-widget">
<a href="https://www.amazon.co.jp/gp/product/{{ $asin }}/?tag={{ $tag }}"></a>
<div class="amazon-widget-img">
<img src="http://images.amazon.com/images/P/{{ $asin }}.09_SL110_.jpg" />
</div>
<div class="amazon-widget-info">
<span class="amazon-widget-title">
{{ $title }}
</span>
<span class="amazon-widget-via">
<img src="https://www.amazon.co.jp/favicon.ico" />
amazon.co.jp
</span>
</div>
</div>
assets/css/extended/amazon.css
.amazon-widget {
margin: 2rem 0;
max-width: 480px;
position: relative;
}
.amazon-widget a {
position: absolute;
top: 0; left: 0;
height: 100%; width: 100%;
}
.amazon-widget-img {
border: 1px solid #E1E8ED;
border-radius: 15px 15px 0 0;
text-align: center;
}
.amazon-widget-img img {
border: none;
margin: 0 auto;
max-height: 200px;
padding: 16px;
}
.amazon-widget-info {
border: 1px solid #E1E8ED;
border-top: none;
padding: 5px 10px 10px 10px;
border-radius: 0 0 15px 15px;
}
.amazon-widget:hover .amazon-widget-info {
background-color: #E1E8ED;
}
.amazon-widget-title {
font-weight: bold;
display: block;
}
.amazon-widget-via img {
width: 18px; height: 18px;
vertical-align: text-bottom;
}
使用例
{{< amazon asin="B0GKLJ7DHD" title="Nintendo Switch 2(日本語・国内専用) + マリオカート ワールド -Switch2 【Amazon.co.jp限定】特典 Nintendo Switch 2 ロゴデザイン マイクロファイバークロス 同梱" >}}
WordPress風ブログカード
内部リンクをカード形式で表示します。site.GetPage を使って、タイトルや概要、アイキャッチ画像を自動取得します。
layouts/shortcodes/blogcard.html
{{- $url := .Get "url" -}}
{{- $title := .Get "title" -}}
{{- $img := .Get "image" -}}
{{- $desc := .Get "description" -}}
{{- $target := "_blank" -}}
{{- /* Check if positional argument is provided (path/url) */ -}}
{{- if .IsNamedParams -}}
{{- /* Use named params */ -}}
{{- else -}}
{{- /* Assumes first positional arg is URL/Path */ -}}
{{- $url = .Get 0 -}}
{{- end -}}
{{- $page := "" -}}
{{- if $url -}}
{{- $page = site.GetPage $url -}}
{{- end -}}
{{- if $page -}}
{{- /* Internal Link Logic */ -}}
{{- if not $title -}}{{- $title = $page.Title -}}{{- end -}}
{{- if not $desc -}}{{- $desc = $page.Description | default $page.Summary -}}{{- end -}}
{{- if not $img -}}
{{- with $page.Params.cover.image -}}
{{- $cover_image := . -}}
{{- $resource := $page.Resources.GetMatch $cover_image -}}
{{- if $resource -}}
{{- $img = $resource.Permalink -}}
{{- else -}}
{{- $img = $cover_image | absURL -}}
{{- end -}}
{{- else -}}
{{- with $page.Params.images -}}
{{- $img = index . 0 | absURL -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /* Use permalink if Page found */ -}}
{{- $url = $page.Permalink -}}
{{- $target = "" -}} {{- /* Internal links often open in same tab, but user might prefer blank if explicitly set */ -}}
{{- end -}}
<a href="{{ $url }}" class="blog-card" {{ if $target }}target="{{ $target }}" rel="noopener noreferrer" {{ end }}>
<div class="blog-card-content">
<div class="blog-card-title">{{ $title }}</div>
<div class="blog-card-desc">{{ $desc | plainify | truncate 80 }}</div>
<div class="blog-card-site">{{ site.Title }}</div>
</div>
{{- if $img -}}
<div class="blog-card-thumbnail">
<img src="{{ $img }}" alt="{{ $title }}">
</div>
{{- end -}}
</a>
assets/css/extended/blogcard.css
.blog-card {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--entry);
border: 1px solid var(--border);
border-radius: 8px;
margin: 1.5rem 0;
padding: 1rem;
text-decoration: none !important;
color: inherit;
transition: transform 0.2s, box-shadow 0.2s;
overflow: hidden;
}
.blog-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border-color: var(--primary);
}
.blog-card-content {
flex: 1;
margin-right: 1rem;
min-width: 0;
}
.blog-card-title {
font-weight: bold;
color: var(--primary);
margin-bottom: 0.5rem;
}
.blog-card-thumbnail {
flex-shrink: 0;
width: 160px;
background: var(--code-bg);
}
@media (max-width: 600px) {
.blog-card { flex-direction: column-reverse; }
.blog-card-thumbnail { width: 100%; margin-bottom: 1rem; }
}
使用例
{{< blogcard "/posts/diary/wordpress-to-hugo/" >}}
Flourish(動的グラフ)の埋め込み
IDを指定するだけで Flourish のグラフを埋め込めるようになります。
layouts/shortcodes/flourish.html
<div class="flourish-embed" data-src="visualisation/{{ .Get 0 }}"></div>
<script src="https://public.flourish.studio/resources/embed.js"></script>
使用例
{{< flourish "12345678" >}}
X(旧Twitter)のポストの埋め込み
Hugoには標準でXのポストを埋め込むためのショートコードが備わっています。以前は tweet でしたが、現在は x という名前でも利用可能です。
使い方
ポストのURLの最後にあるIDをコピーして指定します。
{{< x "1673907275338104832" >}}
hugo.yaml の設定
デフォルトではXのリッチなウィジェットを読み込みますが、プライバシー保護や表示速度向上のために hugo.yaml で「simple」設定を有効にすることをおすすめします。
privacy:
x:
simple: true # 投稿内容をAPI経由で取得せず、シンプルなHTML(引用タグ)のみにする
enableDNT: true # トラッキングを防止(Do Not Track)
これにより、外部との通信を最小限に抑えつつ、Googleが推奨するセマンティックなマークアップで表示されます。
5.4. デザイン微調整 (CSS)
全般のデザイン調整
assets/css/extended/custom.css に記述することで、見出しやテーブルのスタイルを自分好みに調整しています。
assets/css/extended/custom.css
/* --- H2: メインセクション --- */
.post-content h2 {
padding: 0.5rem 0 0.5rem 12px;
border-left: 6px solid var(--secondary);
border-bottom: 1px solid var(--border);
background: rgba(var(--secondary), 0.05);
}
/* --- テーブルのデザイン --- */
table {
width: 100%;
border-collapse: collapse;
margin: 2rem 0;
border: 1px solid var(--border);
}
table thead { background-color: var(--code-bg); }
table th, table td {
padding: 12px 15px;
border-bottom: 1px solid var(--border);
}
table tbody tr:nth-of-type(even) {
background-color: rgba(128, 128, 128, 0.05);
}
フッタのカスタマイズ
標準のフッタをオーバーライドし、独自のリンク集とスタイリングを適用しました。
layouts/partials/footer.html
<footer class="footer">
<div class="footer-links">
<a href='{{ "/privacy-policy" | relURL }}'>プライバシーポリシー</a>
<a href='{{ "/contact-us/" | relURL }}'>お問い合わせ</a>
</div>
<span>© {{ now.Year }} <a href='{{ "/" | absLangURL }}'>{{ site.Title }}</a></span>
</footer>
assets/css/extended/footer.css
.footer {
text-align: center;
padding: 2rem 0;
}
.footer-links {
margin-bottom: 1rem;
}
.footer-links a {
margin: 0 10px;
color: var(--secondary);
text-decoration: none;
}
.footer-links a:hover {
text-decoration: underline;
}
5. 設定ファイル(hugo.yaml)
このサイトでの設定例でs.
baseURL: https://mildinvestor.com/
languageCode: ja
defaultContentLanguage: ja
title: お金の育て屋さん
theme: PaperMod
taxonomies:
category: categories
tag: tags
permalinks:
page:
posts: /:slug/
params:
description: お金の不安を感じる方の力になりたい。お金と健全に付き合うための知識と考え方。
defaultTheme: auto
disableThemeToggle: false
showShareButtons: true
showReadingTime: true
showToc: true
tocopen: true
showBreadCrumbs: true
showCodeCopyButtons: true
comments: false
hideFooter: false
DateFormat: '2006-01-02'
mainSections:
- posts
assets:
favicon: /favicon.ico
disableHLJS: true
googleAdSense: ご自身のものを記入
googleAnalytics: ご自身のものを記入
amazonJpAffiliate: ご自身のものを記入
copyright: "© 2019-2026 お金の育て屋さん"
markup:
highlight:
codeFences: true
guessSyntax: true
style: monokai
goldmark:
renderer:
unsafe: true
outputs:
home:
- HTML
- RSS
- JSON
outputFormats:
RSS:
mediaType: application/rss+xml
baseName: feed
menu:
main:
- name: 全記事一覧
url: /all/
weight: 10
- name: 検索
url: /search/
weight: 20
- identifier: categories
name: カテゴリ
url: /categories/
weight: 30
- identifier: tags
name: タグ
url: /tags/
weight: 40
- name: プロフィール
url: /profile/
weight: 50
privacy:
x:
simple: true
enableDNT: true
6. 公開とドメイン設定(Cloudflare連携)
ローカルで動作確認がとれたらサイトを公開します。
- GitHubへプッシュ: ローカルのHugoプロジェクトをGitHubのリポジトリへアップロード。
- Cloudflare Pagesと連携:
- Cloudflare管理画面からWokers/Pagesを選択する。その後「Pages」を選び、HugoサイトのGitHubリポジトリを選択。
- ビルド設定: フレームワークで「Hugo」を選択する。
- カスタムドメインの設定: Cloudflareで自分が所有しているドメインを紐づけ。
- 動作確認: AdSense広告が表示されているか、お問い合わせフォームが機能するかを最終確認。
7. まとめ
今回のカスタマイズを通じて、Hugo は「シンプルでありながら拡張性が非常に高い」ことを実感しました。特に PaperMod のようなメジャーなテーマであれば、生成AIとの協力で思い通りのデザインや機能を効率的に実装できます。