Claude Codeを使ってWordPressサイトを静的サイトに移行:午後一つで再構築した例

公開日
2026年7月11日
著者
Jacob Lloyd — プロジェクト完了後、AIの支援を受けて執筆
読了時間
約13分で読めます

かんたんに言うと: このウェブサイトが、AIコーディングアシスタントによってたった一午後に再構築された経緯を紹介します。元々はWordPressで作られていましたが、最終的には単純なファイル群のフォルダへと変わりました。新しいサイトはより高速で、運用コストもかからず、さらにハッカーが攻撃できるようなログインページも存在しません。最後には、読者の皆様がご自身のサイトでも同じことを実行できるための手順を詳しく説明しています。

このサイトは以前、WordPressで運用されていました。現在では、Markdownファイルの集まりと183行からなるPythonスクリプトだけとなっており、これらはAIコーディングエージェントが午後のうちにWordPressのエクスポートデータから作成したものです。どのようにしてこれが実現されたのか、またなぜ同じワークフローで今もサイトが運営されているのかをご紹介します。

要約
  • 概要: WordPressに代わって使用される、ゼロから作られた静的サイト生成ツール(Python + Jinja2)であり、旧コンテンツ用のインポーターも併せて用意されています
  • 費用: 無料です。プラグインやテーマライセンスも不要で、以前と同じ安価な共有ホスティングを利用できます
  • 必要なもの: WordPressのエクスポートデータ(WXRファイル)、アップロード用フォルダ、そしてAIコーディングエージェント。私はClaude Codeを使用しました
  • 完成形: データベース不要の単純なHTMLファイル群となり、ブルートフォース攻撃の標的となるログインページも存在しません

最終的にできあがったもの

まずはレシート、つまり再構築が実施された日の実際の端末出力です:

$ python3 _build/build.py
Built 15 files (7 projects, 3 pages, 4 categories).

移行時点では、サイト全体でHTMLページが15ページだけでした。ホームページ、カテゴリーページ4つ、プロジェクト記事7件、情報ページ3つです。すべて事前にレンダリング済みの単なるファイルであり、リクエストごとに実行されるサーバーサイドコードは一切ありませんでした。

元となったWordPressのエクスポートデータは572KBで、123項目が含まれていました。投稿記事やページ、画像ファイル、そして多くの未完成の下書きも含まれていました。実際に移行できたのは7つの投稿のみで、残りはインポーターが意図的にスキップした不要なデータでした。

そして、このサイトを構築するための仕組みは、テーブル1つ分の大きさしかありません:

構成要素行数
build.py(ジェネレーター)183
migrate.py(一回限りのインポーター)166
テンプレート(5種類のページレイアウト)139
パーシャル(ナビゲーション、フッター、カードなど)51
site.yaml(サイト設定全体)36
serve.sh(ローカルプレビュー用)12
.htaccess(サーバールール)57
合計644

CSS(322行)とJavaScriptファイル1つ(31行。モバイル用のナビゲーション切り替え機能のみ)を加えても、サイト全体のコード量は1,000行未満です。node_modulesもなければ、ビルドフレームワークもなく、争うべきロックファイルも存在しません。

第1部:WordPressからの移行

この内容は私が手書きしたものではありません。私はClaude Codeに望むことを説明したところ、インポーターやジェネレーター、テンプレートまで作成してくれました。私の役割は出力結果を確認し、「いえ、こうするべきです」と指摘することだけでした。ファイルのタイムスタンプがそれを裏付けています。わずか2時間足らずで、WordPressのエクスポートデータから実際に閲覧可能なサイトが完成しました。週末ではなく、たった午後の作業で済んだのです。この「説明して、レビューする」という手順は、LLMに任意のプロジェクトを自分のシステムに適応させる方法でも紹介しているやり方と同じです。

最終的に採用された処理フローは以下の通りです:

エクスポートとメディアミラー

WordPressでは、ツール → エクスポート → すべてのコンテンツを通じて全データがエクスポートされます。その結果、投稿やページ、カテゴリー、添付ファイルの情報が含まれたWXRファイル(XML形式)が得られます。これがインポーター側への唯一の入力データとなります。

ただし、このエクスポート処理では実際の画像ファイル自体は含まれず、URLのみが記載されます。そのため、アップロードされた画像ファイル群は別途取得する必要があります。SFTPやファイルマネージャー、あるいは単純なwgetミラー機能などを利用して、assets/uploads/ディレクトリに保存します。この際、WordPressが使用していた年/月の構造を維持することで、書き換えられたURLが正しく解決されるようにします。

migrate.pyが実際に行うこと

このインポート処理は一度きりですが、安全に何度でも実行可能です。XMLライブラリは使用せず、WXRテキストに対して正規表現のみを利用しています。一見不安定に思えますが、WordPressのエクスポート用XMLは極めて規則的であるため、完全なパーサーを実装するよりもはるかに少ないコード量で処理できます。各投稿からタイトル、スラッグ、日付、本文、カテゴリ、およびアイキャッチ画像を抽出した後、以下の処理を行います。

  • 不要なデータをスキップ — 空の投稿や「(下書き)…」というタイトルの投稿はcontent/フォルダに保存されません
  • カテゴリをマッピング — WordPressの煩雑なニックネームではなく、簡潔で整理されたキーに変換します
  • アイキャッチ画像を特定 — 添付ファイルIDを使って特定し、見つからない場合は本文内で最初にアップロードされた画像を使用します
  • メディアURLを書き換え — wp-content/uploads/…の形式を /assets/uploads/…に変更します
  • 内部リンクを書き換え — すべての投稿・ページから作成したスラッグマップを利用し、古いパーマリンクを新しいきれいなURLに置き換えます
  • Gutenbergブロックのコメントを削除 — 動的なリスト表示用ブロックも完全に除去します。現在はビルド処理でカテゴリ一覧が自動生成されるため、古いブロックは単に時代遅れなまま残るだけだからです
  • 抜粋を自動生成 — HTMLタグを除去し、160文字程度に切り詰めてカード用テキストやメタ記述として利用します

興味深い点は、処理後の投稿がMarkdownファイル内に含まれたクリーンなWordPress HTMLの形で出力されることです。つまりMarkdown構文に変換されているわけではありません。レンダラーは生のHTMLをそのまま通すため、元のコンテンツが損なわれることは一切ありません。新規投稿は通常のMarkdown形式で記述されます。どちらも同じフォルダから問題なくビルドできます。初めて「.md」ファイルを開いた際、その90%がHTMLタグだったので何か不具合が起きたのかと心配しましたが、実際には何も問題ありませんでした。

build.pyの役割

このスクリプトが実際に永続的に実行される部分です。site.yamlからナビゲーションやカテゴリー、リンク情報を読み取り、content/内のすべてのファイルを取得します。そして各ファイルをJinja2テンプレートを通してレンダリングし、index.htmlを含む実際のフォルダーとして生成します。サーバーのリライト設定なしでも適切なURLが得られる仕組みです。ページ自体の生成に加えて、以下のものも作成されます。

  • データに基づいて自動生成されるナビゲーションバーとフッター — カテゴリーやページを追加すれば自動的に表示される
  • 各ページ用のタイトル、メタ記述、正規URL、Open GraphタグおよびTwitter用タグ
  • 毎回実行時に再生成されるsitemap.xml、robots.txt、ウェブマニフェスト
  • 古くなった出力ファイルを先に削除するクリーンアップ処理 — 投稿名を変更しても不要なページが残らない
  • 実行時の依存関係が一切不要 — フォントは自己ホスティング、アイコンはインラインSVG、JavaScriptファイルも1つだけ。CDNからの読み込みは一切ありません

新しい投稿を追加するには、content/projects/にMarkdownファイルを置き、ビルドを実行するだけです。これが全てのワークフローとなります。

プレビューしてからデプロイします

ローカルでのプレビューは、ビルドとサーバー起動を一つのスクリプトで行います。実際に本番サーバーにアップロードする前に、すべてのページを確認しましょう。

./serve.sh
# builds, then serves the whole site at http://localhost:8099

デプロイ処理では、何が配信されるか(HTML、CSS、JS、画像、フォント、.htaccess)や何が配信禁止となるか(Pythonスクリプト、Markdownソース、WordPressのエクスポートデータ、テンプレート)を明示したマニフェストに基づき、rsyncでファイルを送信します。サーバー側の設定では圧縮処理やキャッシュヘッダー、セキュリティヘッダーが適用されるほか、ソースファイルを直接取得することを厳しく禁止しています。そのため、たとえ生成ツールの存在を知っている人がいたとしても、ダウンロードできるものは何もありません。

第2部:現在、このサイトはどのように維持されているか

再構築を行う価値があった理由はこうです。今では、このサイトももはや手作業で維持されていません。サイトを構築したのと同じエージェントワークフローが、現在もサイトを最新の状態に保っているのです。つまり、皆様が今読んでいるこの更新情報も、その結果なのです。

今回の改修作業は「監査スウォーム」から始まりました。あるチームが公開されているすべてのページを事実確認し、外部へのリンクも一つひとつクリックして確かめました。また別のチームは、サイトの動作に関するスクリーンショットや技術的な詳細情報を収集しました。これらすべてが一つの仕様書にまとめられ、「こう作るべきだ、数字は勝手に作ってはいけない、機密情報は絶対に公開してはいけない」という指示が定められました。

その後、作業は広がっていきました。プラットフォーム用のエージェント(カテゴリ構造や第二言語の実装、検索エンジンの仕組みなどを担当)、デザイナー、そして各記事ごとに執筆用のエージェントが配置されました。まず高速なモデルが、提供された情報に基づいて各ページの下書きを作成します。次により性能の高いモデルが文体や正確性をチェックして編集します。さらに「敵対的検証」の工程では、元の事実情報と照らし合わせて数字を再確認したり、リンクを再度クリックしたりして、作為的な記述やAIが書いたと思われる内容がないかを調べます。実際、今回の記事もAIによって作成されたものです。

自分のエージェントにこの作業をさせましょう

永遠に修正し続けたくない古いWordPressサイトをお持ちですか?これが私が自分のエージェントに指示した手順です。各ステップをエージェントに説明して、コードを書いてもらいましょう。

  1. エクスポートする。 WordPress管理画面から ツール → エクスポート → すべてのコンテンツ を選択します。生成されたWXRファイルを、エージェントが読み取れる場所に保存してください。例えば _build/source.xml のようにです。
  2. メディアをミラーリングする。 古いサーバーから wp-content/uploads/ 内のファイルを、年/月の構造を維持したまま assets/uploads/ にコピーします。
  3. 移行用スクリプトを依頼する。 WXRファイル内の項目を解析し、下書きや空の項目はスキップします。カテゴリーを簡潔なリストにマッピングし、アイキャッチ画像があればそれを使用、なければ最初のインライン画像を代用します。アップロード先URLや内部リンクも書き換え、Gutenbergブロック関連のコメントは削除し、抜粋文も自動生成した上で、Markdown形式のフロントマター付きファイルを作成します。
  4. ジェネレーターを依頼する。 サイト設定とコンテンツフォルダーの内容を読み込み、テンプレート(ベース、ホーム、カテゴリー、投稿、ページ、共通ナビゲーション/フッター/カードパーツ)に基づいてレンダリングします。見やすいURL構造のフォルダーを生成し、sitemap.xmlやrobots.txt、ウェブマニフェストも作成します。
  5. デザインを一から再構築する。 古いテーマのCSSはそのまま使わず、シンプルなスタイルシートを用意します。オープンソースのフォントを自前でホストし、SVGアイコンをインラインで使用することで、外部へのリクエストを一切発生させません。
  6. すべてをプレビューする。 ビルドからローカルでの配信までを行う1行のスクリプトを用意します。各ページを順にクリックして確認しましょう。
  7. サーバー設定を記述する。 圧縮処理やキャッシュヘッダー、セキュリティ関連のヘッダーを設定します。ソースファイルへのアクセス制限や、拡張子なしのURL用のフォールバックルールも定義します。
  8. マニフェストを用いてデプロイする。 出力結果をrsyncで転送しますが、ソースフォルダーやスクリプトは明示的に除外します。その後、公開されたURLが200応答を返すか確認します。
  9. エクスポートファイルは残しておく。 WXRファイルはプロジェクト内に保持しておきます。将来再インポートが必要になった際には、移行用スクリプトを再度実行することでコンテンツフォルダーをきれいに再生成できます。

実行に必要な依存ライブラリの一覧は以下の通りです:

pip install jinja2 pyyaml markdown

以上です。

注意点

  • アップロード用フォルダをミラーリングするのを忘れると、何の警告も出ません。 ビルド自体は成功しますが、すべてのサムネイルが404エラーになります。私は手探りでこの事実に気づきました。
  • 出力ファイルとソースコードが同じフォルダに保存されます。 ビルドされたHTMLファイルは、PythonやMarkdownのソースと同じプロジェクトルートに配置されます。デプロイ時の分離は「許可/拒否リスト」のみで制御されます。最初は逆だと感じましたが、これにより単純なrsyncでのデプロイが可能になっています。
  • カテゴリ名を変更すると、不要なフォルダが残ってしまうことがあります。 クリーンアップ処理ではカテゴリ名が直接記述されています。設定でカテゴリ名を変更した際にそのリストを忘れると、古いフォルダがそのまま公開された状態で残ってしまうのです。
  • 日付は文字列としてソートされ、日付として扱われません。 すべての日付が YYYY-MM-DD 形式で記述されていれば問題ありません。ただし、一箇所でも書式を間違えると最新の投稿が静かにリストの最下部に追いやられてしまいます。

関連記事: 現在AIエージェントがどのようにこのサイトを管理しているか、WordPressを使っていた頃に行っていたこと、LLMを使って任意のプロジェクトを自分のシステムに適応させる方法、そして OpenClawが壊れた? Claude Codeで直す方法。


← AIとローカルLLMをもっと見る