🚀

WordPressサイトをAstroへ移行。4700記事のビルドは10秒!

に公開

3秒まとめ

  • 約4700記事の WordPress サイトを Astro へ移行し、ビルド時間はわずか10秒を実現
  • wordpress-export-to-markdownを使ってコンテンツを自動変換、Claude Code で効率的にデータ処理
  • Cloudflare Pages で無料ホスティング、Lighthouse スコア 100 点達成
  • 全文検索機能も pagefind で実装、トータルの移行作業は数時間で完了

どんな人向けの記事?

  • WordPress サイトを静的サイトジェネレーターへ移行したい方
  • 大量のコンテンツを効率的に移行する方法を知りたい方
  • Astro を使った実践的な移行事例を参考にしたい方
  • サーバー運用コストを削減したい方

概要

前回、WordPress で作られた写真ポートフォリオサイトを Astroに移管してみたときにメリットしかなかったので、引き続きWordPressサイトをAstroに移管していきます。

移管が簡単なサイトから順番に置き換えていく方針です。

今回移行したサイトはこちら:
https://covid19.teraren.com/

UIの変化

まずは見た目の変化を確認しましょう。

Before

トップページ

個別ページ

After

トップページ

個別ページ

移行手順の概要

WordPress から Astro への移行手順を以下にまとめます。各ステップの詳細はこの後に説明します。

  1. 移行前サイトの特性理解
  2. Astroのテンプレート探し
  3. Astroプロジェクトの初期化
  4. WordPressからのフルバックアップ取得
  5. バックアップデータのAstroへの復元
  6. Astro上での各種調整
  7. Cloudflare Pagesへのデプロイ

0. 移行前サイトの特性理解

記事数は約4700記事。毎日 × 10 都道府県 × 2 年間のデータ(期間や都道府県に歯抜けが多い)で構成されています。
各記事の文章は短く、画像のグラフが重要なコンテンツとなっています。

URL構造を維持する必要があります。記事数が膨大なため、リダイレクトではなく同じ URL フォーマットを保持します。

URL フォーマット:
https://covid19.teraren.com/<year>/<month>/<day>/<slug>/

URL 例:
https://covid19.teraren.com/2022/09/25/ishikawa-560/

PV はほとんど無いサイトなので、サクッとデータを移行してサーバー運用を楽にしたいというのが今回の狙いです。

1. Astroのテンプレート探し

Astro公式サイトのテーマライブラリから、Freeのフラグをオンにしてサクッと探しました。

今回見つけたのがこちらの Astro News。シンプルなUIでニュースサイト用のUXを提供するテーマです。

2. Astroプロジェクトの初期化

今回は公式提供のテーマではないため、GitHub リポジトリを直接 clone して使います。MIT ライセンスなので安心して利用できます。

git clone git@github.com:Mrahmani71/astro-news.git

完成品をcloneしているので、あとは自分好みにパパっと変更していくだけです。

リポジトリをcloneしただけだとパッケージが古い可能性があるため、最新版にアップデートしておきます。

npm update

3. WordPressからのデータエクスポート

WordPressの管理画面から「ツール」→「エクスポート」を選択してデータをエクスポートします。

でも大丈夫! 次のステップで使用するツールが、XMLファイル内のアセットURLを検知して自動的にローカルにダウンロードしてくれます。優秀!

エクスポートファイルは covid19.WordPress.2025-10-23.xml のようなファイル名で保存されます。

4. データのMarkdown変換

wordpress-export-to-markdownを使ってWordPressのデータをMarkdownに変換します。

このツールを実行すると、エクスポートファイルの場所や展開方法について対話形式で質問されるため、順番に答えていきます。

今回は投稿のタイトルやカテゴリ、画像ファイルが整理された状態で取得できれば十分だったため、シンプルな構成を選択しました。

> npx wordpress-export-to-markdown

Starting wizard...
 Path to WordPress export file? covid-yukimatsukura.WordPress.2025-10-24.xml
 Put each post into its own folder? No
 Add date prefix to posts? No
 Organize posts into date folders? No
 Save images? All Images

Parsing...
54 normal posts found.
1 pages found.
81 attached images found.
55 images scraped from post body content.

Saving posts...
55 posts to save.
 [post] shou-ren-in
 [post] umbrella
 [post] maple-on-the-moss
 [post] tanuki-ko
 [post] skytree
 [post] skytree-2
 [post] tokyo-tower
 [post] summer-morning
 [post] relais-la-suvera
 [post] ginza
 [post] relais-la-suvera
 [post] colosseo
 [post] grotta-azzurra
 [post] venetia
 [post] positano
 [post] eiffel-tower

すべての画像データが自動的にダウンロードされます。
容量は約800MBありました。Cloudflare Pagesの無料プランでホスティングする予定だったため、上限に引っかかる可能性を心配していましたが、問題なく無料枠でデプロイできました。

5. Astroへのデータインポート

約4700記事あるため、CLIで効率的にデータをコンバートする必要がありました。shellとの相性が良いClaude Codeを使用してコンバート処理を実装しました。

プロンプトは以下のように簡潔に書きましたが、これですべての記事を自動変換できました。

こちらのサイト(https://covid19.teraren.com/) をAstroで再構築します。このレポジトリを基本として進めます。 covid19.teraren.com の全部の記事はexportして covid19-WordPress.2025-10-29.xml に保管してあります。
このXMLファイルを処理して、markdownでコンバートして、画像ファイルを保管したディレクトリが、 `output/`
以下になります。

このコンテンツをカレントディレクトリのAstroのレポジトリでインポートしてください。XMLファイルは巨大なので一気に読み込まないでください。covid19.teraren.com をチェックしてもらえればわかると思いますが、コンテンツとしては4000ページあります。整理の方法は、日本の都道府県ごとに、1日1つの記事が生成されます。記事の内容は短いです。

記事には1つの画像があり、その画像が重要な画像となります。

Claude Codeは、データのコンバートと同時に各ページを表示するAstroファイルも生成してくれました。処理の後半で細かな実装方法について質問されたため、いくつか回答するだけで完了しました。

画像ファイルの配置方法

大きな論点として、画像ファイルをどう管理するかという点がありました。

選択肢:

  • src/ 以下に配置してAstroの画像最適化を利用する
  • public/ 以下に置いて単純に読み込む

前者が理想的ですが、ビルド時間が長くなること、そしてサイトへのアクセスがほとんど無いため最適化の優先度が低いという理由で、public/ に配置する方針を選択しました。

この方法により、コンテキストの消費を抑えつつ、高速にインポートできました。データをGit add, git commitする際は、ファイル数が多いため約1分かかりました。

作られたファイルは以下のような構造です。

❯ fd mdx src/|head -n 50
src/content/views/error404.mdx
src/content/views/authors.mdx
src/content/views/articles.mdx
src/content/views/categories.mdx
src/content/views/author.mdx
src/content/views/home.mdx
src/content/views/search.mdx
src/content/views/contact.mdx
src/content/covid19/2021/12/ishikawa-295.mdx
src/content/covid19/2021/12/kanagawa-384.mdx
src/content/covid19/2021/12/kanagawa-390.mdx
src/content/covid19/2021/12/kanagawa-385.mdx
src/content/covid19/2021/12/ishikawa-294.mdx
src/content/covid19/2021/12/gifu-269.mdx
src/content/covid19/2021/12/ishikawa-296.mdx
src/content/covid19/2021/12/kanagawa-378.mdx
src/content/covid19/2021/12/kanagawa-387.mdx
src/content/covid19/2021/12/kanagawa-386.mdx
src/content/covid19/2021/12/kanagawa-379.mdx
src/content/covid19/2021/12/ishikawa-297.mdx
src/content/covid19/2021/12/ishikawa-287.mdx
src/content/covid19/2021/12/ishikawa-293.mdx
src/content/covid19/2021/12/kanagawa-369.mdx
src/content/covid19/2021/12/kanagawa-382.mdx
src/content/covid19/2021/12/kanagawa-383.mdx
src/content/covid19/2021/12/kanagawa-368.mdx
src/content/covid19/2021/12/ishikawa-292.mdx
src/content/covid19/2021/12/ishikawa-290.mdx
src/content/covid19/2021/12/kanagawa-381.mdx
src/content/covid19/2021/12/kanagawa-380.mdx
src/content/covid19/2021/12/ishikawa-291.mdx
src/content/covid19/2021/12/tokyo-383.mdx
src/content/covid19/2021/12/tokyo-354.mdx
src/content/covid19/2021/12/tokyo-368.mdx
src/content/covid19/2021/12/mie-187.mdx
src/content/covid19/2021/12/mie-186.mdx
src/content/covid19/2021/12/tokyo-369.mdx
src/content/covid19/2021/12/tokyo-355.mdx
src/content/covid19/2021/12/tokyo-382.mdx
src/content/covid19/2021/12/tokyo-380.mdx
src/content/covid19/2021/12/tokyo-357.mdx
src/content/covid19/2021/12/mie-184.mdx
src/content/covid19/2021/12/kochi-2.mdx
src/content/covid19/2021/12/mie-185.mdx
src/content/covid19/2021/12/tokyo-356.mdx
src/content/covid19/2021/12/tokyo-381.mdx
src/content/covid19/2021/12/fukui-189.mdx
src/content/covid19/2021/12/fukui-172.mdx
src/content/covid19/2021/12/tokyo-352.mdx
src/content/covid19/2021/12/mie-181.mdx

画像ファイルは以下のような構造になります。

❯ fd 'gruff.*png' public/ | head -n 20
public/images/hero/gruff20220926-1-r4c6mc-cut.png
public/images/2022/06/gruff20220624-1-okz7x0.png
public/images/2022/06/gruff20220607-1-h6ieww.png
public/images/2022/06/gruff20220629-1-dv16k9.png
public/images/2022/06/gruff20220625-1-7l432r.png
public/images/2022/06/gruff20220605-1-n4hq4x.png
public/images/2022/06/gruff20220623-1-tumiku.png
public/images/2022/06/gruff20220618-1-6cb8hw.png
public/images/2022/06/gruff20220625-1-708n0u.png
public/images/2022/06/gruff20220626-1-pqm5nt.png
public/images/2022/06/gruff20220624-1-udulbu.png
public/images/2022/06/gruff20220605-1-ramra9.png
public/images/2022/06/gruff20220622-1-qpzcon.png
public/images/2022/06/gruff20220618-1-bb9fyw.png
public/images/2022/06/gruff20220604-1-gupi4z.png
public/images/2022/06/gruff20220602-1-90m8te.png
public/images/2022/06/gruff20220602-1-jybrnb.png
public/images/2022/06/gruff20220605-1-b613w6.png
public/images/2022/06/gruff20220601-1-r4d1em.png
public/images/2022/06/gruff20220606-1-pw5yvo.png

全文検索機能の実装

pagefindを使って、静的サイトでも全文検索できるようにします。

Astroとのインテグレーションキットがあるため導入は簡単です。インクリメンタルに全文検索が動作します:
https://covid19.teraren.com/search/?q=東京

全文検索用のインデックスはビルド時に作成されます。今回は約3分(206秒)かかりました
結構時間がかかるため導入はトレードオフですね。今回のサイトでは全文検索のユースケースはほとんど無いと思いますが、将来的な拡張性を考慮して実装しました。

02:28:45.462	17:28:45 [@astrojs/sitemap] `sitemap-index.xml` created at `dist`
02:28:48.462	17:28:48 [build] Waiting for integration "pagefind", hook "astro:build:done"...
02:29:03.469	17:29:03 [pagefind] Pagefind indexed 5222 pages
02:29:03.476	17:29:03 [pagefind] Pagefind wrote index to /opt/buildhome/repo/dist/pagefind
02:29:03.477	17:29:03 [build] 5222 page(s) built in 206.27s
02:29:03.477	17:29:03 [build] Complete!

ビルドされたインデックスファイルは静的ファイルとしてデプロイされ、ブラウザからダウンロードされます。検索はブラウザ上のJavaScriptランタイムで実行される仕組みです。
ダウンロードされたインデックスサイズは約70KBと軽量でした。

サーバサイドに保存されているindexはファイルごとに分割されいて、19MBでした。

❯ du -hs dist/pagefind/
 19M    dist/pagefind/

6. Cloudflare Pagesへのデプロイ

Cloudflare PagesでGitHubと連携して、今回作成したリポジトリを指定します。リポジトリのタイプをAstroと選んでデプロイボタンをポチッと押したら、約6分後にデプロイ完了しました。

データ容量が約 1GB、pnpm build で出力されたファイル数は1万以上あるため、ネットワーク転送とディスク I/O に時間がかかっています。

今回もほとんど更新が無い静的コンテンツのため、CloudflareのCDNを有効化して配信しています。

パフォーマンスベンチマーク

今回は大きな写真データは無いですし、UI もこだわっていないのでパフォーマンスが良いです。

トップページのLighthouseスコア

個別ページのLighthouseスコア

画像関連の最適化を入れていないのでスコアは少し低めに出ています。

ビルド時間の内訳

約4700ページ分のデプロイ時間は約7分でした。
内訳は以下のとおりです:

  • データのダウンロード+ビルド環境の構築:1分
  • ページの生成: 10秒
  • 全文検索インデックスの作成:3分
  • ファイルのデプロイ: 2分

意外にも、全文検索機能のインデックス作成が一番時間がかかる処理になっています。ページの生成自体は驚異的な速さです。

実際に使ってみた感想

良かった点

圧倒的なビルド速度

  • 4700ページのビルドがわずか10秒で完了
  • 開発サーバーの起動も速く、ファイル変更時のホットリロードもサクサク動作
  • プロダクション環境では静的 HTML を配信するため、サーバーサイドの処理が不要

自動最適化が優秀

  • 画像の遅延読み込み、CSS の最小化など、Web パフォーマンスに必要な最適化を自動で実施
  • 特別な設定なしで Lighthouse スコア 100 点を達成

モダンな開発体験

  • 生の HTML に近い形でレンダリングを高速化しつつ、開発体験は React ライク
  • 必要な部分だけ JavaScript を使う「アイランドアーキテクチャ」により、パフォーマンスと開発効率を両立

運用コストゼロ

  • Cloudflareの無料プランで運用可能、月額費用ゼロを実現

課題と改善点

全文検索のビルド時間

  • pagefind のインデックス作成に約3分かかる。手動でビルドして Git に入れるような運用でも良いが、pagefind がそのようなワークフローに対応していなさそうなので面倒そう。
  • 全文検索が不要なサイトでは、このステップを省略することでビルド時間を大幅に短縮できる

まとめ

約4700記事の WordPress サイトを Astro へ移行し、想像以上にスムーズに完了しました。

wordpress-export-to-markdown などの専用ツールが充実しているため、技術的なハードルは低く、数時間程度の作業で移行が完了しました。

得られた成果

  • 圧倒的なパフォーマンス向上 - Lighthouse スコア 100 点達成
  • ビルド時間の劇的な短縮 - 4700 ページが 10 秒でビルド完了
  • 保守性の向上 - Git 管理で変更履歴を追跡可能に
  • 運用コストゼロ - Cloudflare Pages で無料ホスティング
  • 開発体験の向上 - 開発サーバーもビルドも爆速
  • ディスク容量の節約 - 3.3GB => 800MB。1/4 になりました。画像に対して複数のサイズのサムネイルを保持しないようにしたので削減できました。

Discussion