14年間WordPressで運用してきたこのブログを、Astro + Cloudflare Workers による静的サイトに移行しました。その作業ログを記録に残しておく意味で、記事にまとめました。
はじめに
このブログは2012年にWordPressで開設し、2019年にはサーバをGoogle Cloud Platformに移行して運用を続けてきました。
しかし、記事を書く頻度に対して、WordPress本体・プラグイン・PHP・サーバOSの更新といったメンテナンス作業の負担が大きく、「書くより維持の方が大変」な状態が続いていました。正直ここ数年は最低限のメンテナンスはしていましたが、記事を書きたくなったときにもサーバ側の更新やWordPressの更新を先にやらないとな…という状態になっており、逆に記事を書くハードルになっていました。さらに、移行作業中に調べてみると、コードブロック内のXMLがプラグインの劣化で一部表示されなくなっていたり、ダウンロードリンクのプラグインが動かなくなって生のショートコードが表示されていたりと、気づかないうちに壊れている箇所がいくつも見つかりました。
そこで、以下を目標に静的サイトへの移行を決めました。
- サーバ・WordPress本体のメンテナンスから解放される(管理画面自体を持たない)
- 記事はmarkdownでGitHub管理し、pushで自動公開する
- 過去記事のURLは維持する(リダイレクトで保全)
移行先の構成
| 移行前 | 移行後 | |
|---|---|---|
| CMS/SSG | WordPress (Bitnami) | Astro + AstroPaper テーマ |
| ホスティング | Google Compute Engine | Cloudflare Workers (static assets) |
| コンテンツ管理 | MySQL | markdown + GitHub |
| デプロイ | - | push → Cloudflare Workers Builds で自動ビルド |
| 全文検索 | プラグイン | Pagefind(AstroPaper標準装備) |
| サーバ費用 | GCE インスタンス代(ほぼ0円) | 0円 |
SSGはHugoとも比較しましたが、記事数が100件程度でビルド速度の差が問題にならないこと、TypeScriptとの親和性から Astro にしました。
移行の方針
一番こだわったのは旧URLの保全です。2012年からのURL資産(検索インデックス・外部リンク)を無駄にしないため、以下をすべて301リダイレクトで新URLに接続しました。
- 記事URL
/YYYY/MM/slug/→/posts/slug/(全101記事) - タグ・カテゴリ
/tag/xxx/・/category/親/子/→ 新タグページ/tags/xxx/ /feed/→/rss.xml、/wp-sitemap.xml→/sitemap-index.xml、著者アーカイブなど
リダイレクトは Cloudflare の _redirects ファイル(400ルール超)で、WordPressのエクスポートデータから自動生成しています。
移行手順
大まかなパイプラインは以下のとおりです。
- WordPressの管理画面から WXR (XML) をエクスポート
- 前処理:
<pre>内の生タグをエスケープするサニタイザを通す(後述のハマりポイント対策) - wordpress-export-to-markdown で markdown に一括変換
- 後処理スクリプト(自作)で以下を修正
- slug・投稿日時をWXRの値で上書き(変換ツールのタイムゾーンずれ対策)
- 画像URLをルート相対に書き換え(
wp-content/uploadsを同一パスのままpublic/に配置) - gist埋め込み・数式・各種ショートコードの残骸を修復
_redirectsの自動生成
- 全記事
draft: trueで投入し、新旧のレンダリング結果を突き合わせてから順次公開
ハマったポイント
変換ツールの日付がUTCで月がずれる
wordpress-export-to-markdown の出力する日付はUTC基準のため、月初深夜に公開した記事(元日の挨拶記事など)が前月扱いになり、/2015/01/... のURLの記事が 2014-12-31 になっていました。日付はWXR側の値(JST)を正として上書きすることで解決しました。
コードブロック内の生XMLがHTMLとして解釈されて消える
昔の記事は <pre> 内にXMLをエスケープせずに書いており、ブラウザにも変換ツールにもタグとして解釈されてコードが部分的に消えていました。移行前のWordPress上でもすでに欠けて表示されていたので、これは移行を機にWXRの原文から復元できた改善点です。
gist埋め込み・Twitter埋め込みが裸のURLになる
WordPressのoEmbed(URLを貼るだけで埋め込みになる機能)はmarkdown変換で単なるURL文字列になってしまうため、gistは <script> 埋め込みに、ツイートは埋め込みblockquoteに、後処理で自動変換しました。汎用のURLは remark-link-card-plus でリンクカード化しています。
Cloudflareの _redirects は「splat以降が全部動的扱い」
/author/* のようなワイルドカード(splat)ルールをファイル前方に置いたところ、それ以降の全ルールが動的リダイレクト扱いになり、動的100件制限を超えてデプロイが失敗しました。splatは必ずファイル末尾に置く必要があります。エラーメッセージからは原因が分かりにくく、一番ハマったポイントです。
wranglerの自動設定がSSRアダプタを入れようとする
wrangler deploy は設定ファイルがないとフレームワークを自動検出して astro add cloudflare(SSRアダプタ)を実行し、Workersで動かない sharp(Node.js の画像処理ライブラリ)の読み込みでビルドが失敗しました。静的サイトなら wrangler.jsonc に assets 設定を書いておけば回避できます。
AIエージェントと一緒に移行した
今回の移行作業は、Claude(AIエージェント)とペアで進めました。特に効いたのが新旧レンダリングの差分チェックです。
移行記事は101記事なので、最悪すべて目視確認できないことはないですが、非常に時間がかかってしまいます。そのため、移行した101記事すべてについて、稼働中の旧サイトのHTMLと新サイトのビルド結果から本文を抽出して突き合わせ、記事ごとに差分スコアを算出。スコアが高い(差分が小さい)91記事は一括公開し、残り10記事だけを人間が目視レビューする、という進め方をしました。前述のハマりポイントの多く(コードブロックの欠損、gistの裸URL化など)は、この差分チェックが機械的に検出したものです。
結果
- 全101記事を移行し、旧URL(記事・タグ・カテゴリ・feed等、sitemap掲載の187 URL)はすべて301リダイレクトで接続。エラー0件を確認
- WordPress・PHP・サーバOSの更新作業が消滅。今後のメンテナンスは依存ライブラリの更新程度
- 記事執筆は「markdownを書いてpush」だけに
- 表示速度も改善。WordPress時代はnginxまわりの最適化をいろいろやっても最初の描画までに少し時間がかかっていましたが、静的ページの配信になったことでそこはほとんど問題にならなくなりました
Lighthouse(トップページ・デスクトップ、シークレットウィンドウで計測)のビフォーアフターも取りました。スコアだけでなく、Largest Contentful Paint・Speed Index・Cumulative Layout Shift の改善がそのまま体感差になっています。
| 指標 | WordPress | Astro |
|---|---|---|
| Performance | 84 | 99 |
| Accessibility | 84 | 100 |
| Best Practices | 73 | 100 |
| SEO | 83 | 100 |
| First Contentful Paint | 0.9 s | 0.6 s |
| Largest Contentful Paint | 1.7 s | 0.8 s |
| Cumulative Layout Shift | 0.126 | 0 |
| Speed Index | 2.2 s | 0.6 s |
なお公平のための補足として、旧サイトにはGoogle AdSenseが入っていましたが、新サイトにはまだ入れていません。スコア差にはその影響も含まれています。

移行前(WordPress)

移行後(Astro)
最後に
2012年から14年間、同じ静的IPで動き続けてくれたWordPressサーバともうすぐお別れです。長い間ありがとうございました。
移行の詳しい設計判断(なぜAstroか、コメント機能をどうしたか等)や各スクリプトについて、気になる方がいればX等で聞いてください。