目次全12項目
このブログを、WordPressから静的サイトに移しました。AstroでHTMLをビルドして、S3とCloudFrontで配信する構成です。きっかけは10月21日に公開されたPHPの脆弱性です。Claude Codeと作業を進め、4日後の10月25日に、サイトを止めずに切り替えました。月額は15.25ドルから1.4ドルほどに下がっています。
いちばん手間がかかったのは、検索の評価や外からのリンクを失わないように、今までのURLを引き継ぐことでした。記事は92本ですが、URLを洗い出すと2,271件ありました。
01 / 1210月21日の脆弱性で、PHPを7時間半止めた#
10月21日、PHPに2つの脆弱性(CVE-2026-109618、CVE-2026-109619)が公開され、修正版の8.2.35が出ました。特に危険とされたのが、ApacheにPHPを組み込むmod_phpの構成です。アプリケーションが動く前の段階で攻撃が届くためで、AWSのLightsailで動かしていたこのブログのWordPressも、この構成でした。
Debianの修正版が出て適用するまでの約7時間半は、PHPを止めて対応しました。その間は、前段のCloudFrontに残っていたキャッシュだけを配信しています。
02 / 12移した理由は、サーバを持つ手間#
修正版を当てれば、今回の件は片付きます。ただ、サーバの手間はそれ以前から続いていました。この2か月だけでも、メモリ不足でデータベースが落ちる、脆弱性を探して回るスキャナのアクセスで重くなる、設定の穴が見つかる、といったことが重なっています。8月にはサーバを作り直したばかりでした。

公開サーバを持つ以上、この手間は続きます。放っておいても気づかないことはありますが、スパムメールやボットの踏み台にされるのは避けたいところです。そこで、公開サーバをなくすことにしました。移行の条件は次の4つです。
- PHPもデータベースもログイン画面も、外に出さない
- 月額を今より下げる(目標は1ドル未満)
- URLを変えない。変わると検索の評価がやり直しになる
- 書き方を変えない。最近の記事は手元のMarkdownで書き、Claude CodeがWordPressに入稿していたので、この流れを残す
03 / 12移したあとの構成#

青い線が記事の公開の流れです。手元で書いた記事をGitHubにpushし、プルリクエストをマージすると、GitHub ActionsがAstroでサイトをビルドします。チェックを通ったものだけがS3に置かれ、マージから公開まではおよそ2分です。チェックしているのは、画像に位置情報が残っていないか、ページが重すぎないか、今までのURLが生きているか、の3点です。
オレンジの線が読者のアクセスです。読者がつながるのはCloudFrontだけで、S3は外から直接読めないようにしてあります。古いURLへのアクセスは、CloudFront Functions(CloudFrontの上で動く小さな関数)が対応表を引いてリダイレクトします。点線は監視で、エラーが増えたときなどにメールが届きます。
公開側で動くプログラムは、このリダイレクトの関数と、コメントを受け付けるLambdaの2つです。どちらもAWSが管理する環境で動くので、OSやPHPの更新は自分でしなくて済みます。
04 / 12Astroを選び、S3とCloudFrontで配信する#
候補は5つ考えました。静的サイトジェネレータのAstro、Hugo、Eleventyと、Cloudflareが出したCMSのEmDash、それにWordPressを手元に残してHTMLだけを書き出す方式です。EmDashは管理画面ごとCloudflare Workersの上で動くのでログイン画面が外に残り、WordPressからインポートするときにURLを保つ仕組みもありません。書き出す方式では手元のWordPressを更新し続けることになるので、この2つは外しました。残る3つから、ブログカードや目次などのコンポーネントを作りやすく、画像の変換も標準で備えているAstroを選んでいます。次点はHugoでした。
外部サイトAstroAstro builds fast content sites, powerful web applications, dynamic server APIs, and everything in-between.ファイルはS3に置き、CloudFrontから配信しています。静的サイトでは定番の組み合わせです。CloudFrontはWordPressの頃から使っていて、キャッシュの制御の仕方も分かっていたので、抵抗はありませんでした。
05 / 12過去の記事も、HTMLのままにせずMDXに変換した#
最初は、過去の記事はWordPressが出力したHTMLのまま移し、新しい記事だけをMarkdownで書くつもりでした。ただ、それではブログカードや商品紹介のボックスを直すたびに、過去の記事と新しい記事の両方に手を入れることになり、見た目も揃いません。この先の保守も考えて、過去の記事もすべてMDX(Markdownの中にコンポーネントを書ける形式)に変換しました。ブログカード、商品紹介のボックス、画像、広告などはAstroのコンポーネントにしてあり、直せば新旧の記事にまとめて反映されます。
変換の元にしたのは、WordPressのエクスポートではなく、公開中のページのHTMLです。ショートコードやテーマ(このブログはCocoon)の機能は、WordPressが描画したあとのページにしか中身が出てこないからです。変換したあとは、タイトルや説明文、公開日時も含めて全記事を元のページと比べて、違いがないかをチェックしました。見出しのアンカー(#toc1など)も同じ順に付け直しています。検索結果から見出しに直接飛んでくる人がいるためです。
画像は、/wp-content/uploads/ のファイルを同じパスのままS3に置きました。古い記事や画像検索がこのパスを指しているからです。置く前に、写真のメタデータは消しています。
06 / 12URLは、記事の数の20倍以上あった#
2,271件の内訳は次の表のとおりです。WordPressは記事のほかにも、アーカイブやフィード、添付ファイルのページといったURLを自動で作りますし、昔のパーマリンクからのリダイレクトも覚えています。
| URLの種類 | 件数 | 新しいサイトでの扱い |
|---|---|---|
| 記事、固定ページ | 95件 | 同じURLで開く |
| アーカイブ(カテゴリー、タグ、日付、投稿者。2ページ目以降も含む) | 376件 | 115件は同じURL、139件はリダイレクト、日別の一覧88件などの122件は廃止 |
| フィード(サイト全体、カテゴリー、タグ、記事ごとのコメントなど) | 338件 | 55件は同じURL、235件はリダイレクト、48件は廃止 |
| 添付ファイルのページ(ページごとのフィードを含む) | 644件 | 349件は画像の載っている記事へリダイレクト、空のフィード295件は廃止 |
| 末尾に「/」がないURL | 385件 | 「/」を付けたURLと同じ扱い |
| 記事IDのURL(/?p=123) | 95件 | 記事へリダイレクト |
| 昔のパーマリンク、過去に設定したリダイレクト | 108件 | 移った先へリダイレクト |
| 壊れたURL(存在しないページなど) | 166件 | すべて404 |
| そのほか(サイトマップ、ファイル、トップページ) | 64件 | 36件は今までどおり、古いサイトマップ28件は廃止 |
このうちサイトマップに載っていたのは300件だけで、残りはSearch Console、アクセスログ、REST API、Redirectionプラグインの設定から拾いました。また、1,357件はWordPressがもともとリダイレクトで返していたURLです。静的サイトでは、こうしたリダイレクトも自分で用意しなければなりません。切り替えたあと本番で全件をチェックし、意図して変えたもの以外はすべて今までどおりであることを確かめました。
件数は多くても、検索から人が来るのはほぼ記事だけです。Search Consoleで直近16か月のクリックを数えると、99%以上が記事と固定ページの95件でした。ほかのURLを残すのは、外からのリンクやブックマーク、フィードリーダー、検索エンジンのクローラーのためです。
いちばん多い添付ファイルのページは、WordPressが画像ごとに作るページと、そのコメントのフィードです。新しいサイトでは、画像の載っている記事へリダイレクトするようにし、中身が空のフィードはなくしました。WordPress 6.4以降の初期設定のように画像ファイルへリダイレクトすると、そこから記事に移れないためです。ただ、アクセスは1日で全件を回るクローラーと思われるものがほとんどだったので、移行の手間を減らしたいなら、いちばん後回しにしてよいURLです。
パーマリンクに日付を入れているなら、新しいサイトで記事のメタデータ(公開日時)からURLを組み立てるときに、タイムゾーンの注意が要ります。WordPressのURLの日付は日本時間ですが、ビルドする環境はUTCのことが多く、GitHub Actionsも標準ではUTCです。公開日時からそのまま日付を取り出すと、日本時間の0時から9時に公開した記事は、URLの日付が前の日になります。このブログでは92本のうち36本がこれに当たりました。移した記事は、元のURLを記事のファイルにそのまま書いておき、日付から組み立てないようにしました。
検索順位への影響は、切り替えたばかりなのでまだ分かりません。
07 / 12コメントと検索は、サーバレスで置き換えた#
コメントは、Lambdaで受け付ける#
外部のコメントサービスを埋め込む方法もありますが、このブログではLambdaで受け付けることにしました。やり方は、Jekyllなどで使われてきたStaticmanと同じです。Staticman自体は開発が止まっていて、動かすにはNode.jsのサーバが要るので、同じ流れをLambdaで作りました。フォームから送られたコメントをLambdaが受け取り、1件ずつファイルにしてGitHubにプルリクエストを出します。マージすれば記事に載り、閉じれば載りません。
入力してもらうのは名前と本文だけで、コメントのファイルにメールアドレスやIPアドレスは残しません。スパム対策は、CloudflareのTurnstile(人間かどうかを判定する仕組み)、人には見えないダミーの入力欄、Lambdaの同時実行数を1に絞る設定の3つです。最後の設定は、大量に送られても1件ずつしか処理しないようにするためです。WordPressにあった既存のコメントも、ファイルにして移しました。
サイト内検索は、ビルド時にインデックスを作る#
サイト内検索にはPagefindを使っています。ビルド時にインデックスを作っておき、読者のブラウザで検索する仕組みなので、サーバは要りません。ただし日本語では注意が必要で、1.5系では日本語の複合語がヒットしなくなっています(Pagefindのissue #1237、10月24日時点で未解決)。このブログは1.4.0に留めています。
08 / 12費用と速さ#
月額は、WordPressの頃の15.25ドル(9月分)から、1.4ドルほど(見込み)に下がりました。内訳は、Route 53が0.5ドルほど、念のため残しているLightsailのスナップショットが0.65ドルほど、S3とCloudFrontが数セントで、これに税がかかります。CloudFrontには常時無料枠(月1TB、1,000万リクエスト)があり、このブログのアクセス(直近30日で75万リクエスト、29GB)なら収まります。スナップショットを消せば0.7ドルほどになり、目標の1ドル未満に届く見込みです。
表示速度はLighthouse(13.5.0、モバイル)で測りました。広告のない確認用サイトでは、LCP(いちばん大きな要素が表示されるまでの時間)が1.2〜1.6秒で、4項目とも100です。広告が入る本番のトップページは、パフォーマンスが96〜100でした。本番のページは広告込みで2.8〜5.4MBありますが、このサイト自体が送っているのは約200KBです。WordPress時代の数値は測っていませんでした。
09 / 12移して不便になったこと#
静的サイトにして、できなくなったことや手間が増えたこともあります。大きいのは次の3つです。
- 人気記事のランキングがなく、管理画面で記事ごとのアクセス数を見ることもできない。どちらもCocoonにあった機能
- 画像をエディタに貼り付けて投稿できない。写真はコマンドで記事のフォルダに入れ、本文に1枚ずつ書く
- カテゴリーとタグを一覧から選べない。記事ファイルの先頭に名前を書く形なので、直感的ではない
ほかにも、次のような違いがあります。
- 公開に約2分かかる。ビルドとチェックが終わるまで反映されない
- 予約投稿がない(まだ作っていない)
- コメントの承認が、GitHubでのマージになった
- コメントに返信がついても、書いた人に通知できない。メールアドレスを聞いていないため
本文を書くこと自体は変わりませんでした。手間が増えたのは、画像やカテゴリーのように、管理画面ならマウスで済んでいた作業です。このあたりは、生成AIの手を借りれば多少は楽になるかもしれません。
10 / 12Claude Codeと4日で切り替えた#
作業はClaude Codeと分担しました。調査、要件の文書化、コードを書いてテストするところまでがClaude Codeの担当です。方針を決めること、AWSを変更するコマンドの実行、プルリクエストのマージは私がやりました。大きな判断は、別のモデル(Claude Fable)にもレビューしてもらっています。
| 日 | やったこと |
|---|---|
| 10/21 | 脆弱性への対応で、PHPを7時間半止める |
| 10/22 | 要件を整理し、5つの候補からAstroに決める。今までのURLを洗い出す |
| 10/23 | 3本の記事で試作し、URLの一覧と照合する |
| 10/24 | 全92本と固定ページを移し、確認用サイトを公開する |
| 10/25 | 本番を切り替え、WordPressのサーバを削除する(スナップショットは残す)。コメントの受け付けを始める |
本番の切り替えは、blog.hiroaki.jpというドメインを、古いCloudFrontから新しいCloudFrontに付け替えるだけです。事前にテスト用のドメインで一度リハーサルしておき、本番では付け替えから14秒で新しいサイトに切り替わりました。
要件を先に文書にし、URLの一覧をテストにした#
Claude Codeに最初に頼んだのは、コードではなく要件定義です。今のサイトの機能を洗い出して、要件を52件、番号付きで書き出しました。以降の作業は「R05をやって」のように番号で指示しています。
そのうえで、2,271件のURLの一覧をテストにしました。Claude Codeがどこを変えても、URLを壊せば公開の前のチェックで止まります。
標準の仕組みを先に調べてもらった#
いちばん困ったのは、Claude Codeが独自の仕組みを作りたがることでした。記事にコンポーネントを書くための独自の記法を考えたり、コメントを独自の形式で保存しようとしたりします。動きはしますが、独自の仕組みは私が保守し続けることになります。
そこで、「Astroの標準機能と公式インテグレーションを土台にし、独自の実装は標準でできないときだけにする」という方針を決めました。新しい機能は、まず定番のやり方を調べてから作ってもらいます。その結果、記事は公式のMDX、コメントはStaticmanと同じ方式、検索はPagefindに落ち着きました。
11 / 12続きの記事で書くこと#
続きの記事では、次のことを書く予定です。
- URLのチェック。2,271件の洗い出し方と、公開のたびに動くテストの中身
- コメントの受け付け。Lambdaの守り方と、GitHub Appの使い方
- Astroで作ったコンポーネント。ブログカード、商品紹介のボックス、広告の読み込み方など
- ハマったところ。最初のデプロイで全ページが503になった、画像が1.16GBに膨らんだ、など
12 / 12まとめ#
PHPの脆弱性をきっかけに、WordPressを4日でAstroベースの静的サイトに置き換えました。公開側にPHPもデータベースもログイン画面もなくなり、OSやPHPの更新も要らなくなりました。手間がかかったのは、記事の20倍以上あったURLです。公開に2分かかる、予約投稿がないといった不便はありますが、月額は1.4ドルほどになりました。





コメント