IT技術ブログ

WordPressから静的サイト(Astro+Cloudflare Pages)へ移行。URLを変えずにSEOを引き継いだ手順と確かめ方

WordPressのブログを、URLを変えずにAstroとCloudflare Pagesの静的サイトへ移行しました。旧URLの自動照合、301の置き場所の分け方、DNSを2段階で切り替えた手順を、つまずいた所も含めて残します。

この記事の結論:WordPress から静的サイトへ移行するとき、SEO(検索の評価)を守るために一番効いたのは「旧サイトのURLを機械で全部書き出し、新サイトのビルド結果と毎回自動で照合する」ことでした。そのうえで、パスの変更は _redirects、ホスト名の統一(www なし・*.pages.dev → www)は Pages Functions と、301 を置く場所を役割で分けました。DNS はネームサーバーの移動と向き先の変更を2回に分け、どちらの段階でもすぐ元に戻せる状態を保っています。

このブログ(やまやまブログ)は、2026年10月まで Xserver 上の WordPress で動いていました。それを Astro で静的なHTMLに書き出し、Cloudflare Pages から配信する形に移しました。この記事は、その作業の記録です。移すべきかどうかの判断ではなく、「移すと決めたあと、何をどの順でやったか」に絞って書きます。

最初に決めた3つのこと

作業に入る前に、変えないものと変えるものを決めました。

項目 決めたこと 理由
記事のURL 1本も変えない(スラッグ・末尾の / を含めて同じ) 外からのリンクと検索の評価を、そのまま残すため
画像のURL /wp-content/uploads/... のパスのまま置く 画像検索や、他サイトに貼られた画像を切らないため
正式なホスト www 付き(https://www.yamayamabloglink.com)に変える 会社サイトとそろえるため。www なしは同じパスの www へ 301

URLを変えないと決めておくと、移行の作業の大半は「同じパスにファイルがあるか」の確認になります。逆に、ここを曖昧にしたまま作り始めると、あとで転送の設定が膨らみます。

手順1:WordPressの旧URLを機械で書き出す

WordPress には REST API があるので、記事・固定ページ・カテゴリー・タグ・メディアを JSON で丸ごと取り出し、export/ に保存しました。この JSON の link が、旧サイトの正式なURLの一覧になります。記事一覧のページ送り(/page/2/ など)は API に出てこないので、記事数から計算して足しています。

人の手で一覧を作ると、カテゴリーやタグのページ、ページ送りが抜けがちです。API から作れば、WordPress 自身が持っているURLをそのまま使えます。

手順2:Markdown に変換し、URLの形をそろえる

記事の本文は、変換用のスクリプトで HTML から Markdown にし、記事ごとのフォルダに置きました。WordPress のスラッグは front matter の slug に入れ、ここから URL を作ります。

Astro の設定では、URLの形を WordPress に合わせています。

astro.config.mjs(抜粋)js
export default defineConfig({
  site: 'https://www.yamayamabloglink.com',
  output: 'static',
  trailingSlash: 'always',          // 末尾の / を必ず付ける
  build: { format: 'directory' },   // /slug/index.html の形で書き出す
});

format: 'directory' にすると、/xampp-setup/ は dist/xampp-setup/index.html として書き出されます。こうしておくと、次の照合が「そのパスに index.html があるか」だけで済みます。

手順3:ビルドのたびに、旧URLと自動で突き合わせる

一番時間をかけたのが、照合のスクリプトです。手順1の一覧のURLを1本ずつ取り出し、ビルド結果の dist/ に同じパスのファイルがあるかを確かめます。

scripts/verify-urls.mjs(考え方だけ抜粋)js
for (const link of oldUrls) {
  const p = decodeURIComponent(new URL(link).pathname);
  let ok = fs.existsSync(path.join(DIST, p, 'index.html'));
  if (!ok) {
    // _redirects で 301 しているパスは、転送先が実在するときだけ OK とする
    const to = redirectTarget(p);
    ok = !!to && fs.existsSync(path.join(DIST, to, 'index.html'));
  }
  if (!ok) missing.push(link);
}

ポイントは、301 で逃がしたURLを「転送先が本当にある場合だけ」合格にしたことです。転送の設定だけ書いて、転送先のページが無い、という取りこぼしをここで止められます。画像も同じように、メディアの一覧とアイキャッチのURLを照合しています。

結果は、ページ 86 件すべてが「そのまま存在する」か「301 で存在するページへ転送される」状態になりました。記事を足したり設定を変えたりしたあとも、npm run build のあとにこのスクリプトを流せば、URLが崩れていないかを数秒で確かめられます。

手順4:301リダイレクトを置く場所を、役割で分ける

転送は2か所に分けて書いています。

置き場所 担当すること 例
public/_redirects パスの変更 カテゴリー名の変更、無くなった記事 → 近い記事
functions/_middleware.js ホスト名の統一 www なし・*.pages.dev → 同じパスとクエリのまま www

ホスト名の転送を Pages Functions で書いたのは、www なしと *.pages.dev の両方を、1か所で同じ規則で扱いたかったからです。

functions/_middleware.jsjs
const CANONICAL = "www.yamayamabloglink.com";
const REDIRECT_HOSTS = new Set(["yamayamabloglink.com", "yamayamabloglink.pages.dev"]);

export async function onRequest({ request, next }) {
  const url = new URL(request.url);
  if (REDIRECT_HOSTS.has(url.hostname)) {
    url.hostname = CANONICAL;
    url.protocol = "https:";
    url.port = "";
    return Response.redirect(url.toString(), 301);
  }
  return next();
}

デプロイごとに発行される確認用のアドレス(<id>.yamayamabloglink.pages.dev)は、転送の対象から外しています。ここまで転送すると、公開前の確認ができなくなるためです。

つまずいたのは、日本語のカテゴリー名でした。旧カテゴリー「筋トレ」のURLは、WordPress が出すリンクでは %e7%ad%8b... と小文字の%表記になっています。_redirects は大文字と小文字を区別するので、大文字の表記だけ書いても小文字のリンクは転送されません。両方の表記を並べて書いて解決しました。

また、末尾の / が無いURL(例:/xampp-setup)は、Cloudflare Pages が自動で /xampp-setup/ へ 308 で転送します。308 も恒久的な転送なので、ここは追加の設定をしていません。

手順5:DNSを2段階で切り替え、サイトを止めない

ドメインの登録は Xserver のまま、ネームサーバーだけを Cloudflare に移しました。一度に向き先まで変えず、2回に分けています。

段階 やったこと このときに表示されるサイト 戻し方
1 Cloudflare に Xserver と同じ DNS レコードを作り、すべてプロキシなし(DNSのみ)にしてから、ネームサーバーを変更 旧 WordPress のまま ネームサーバーを Xserver に戻す
2 www と www なしの A レコードを消し、yamayamabloglink.pages.dev への CNAME(プロキシあり)に変更 新サイト 2つを元の A レコード(DNSのみ)に戻す

段階1は、利用者から見ると何も変わりません。ネームサーバーの反映を待つ間も、どちらの DNS を引いても同じ WordPress が表示されます。段階2は Cloudflare の管理画面の中だけで完結するので、問題があれば数分で戻せます。旧サーバーの契約は、問題がないと確かめられるまで残しています。

切り替え前の DNS の値は、作業手順書に表で残しました。切り戻しに必要なのは、結局この表だけです。

切り替えたあとに確かめたこと

  • www の各URLが 200 で返ること、www なしと *.pages.dev が同じパスとクエリのまま www へ 301 すること(curl -I で確認)
  • 旧カテゴリーのURLが、新しいカテゴリーのURLへ 301 すること
  • /feed/ が RSS として返ること、ads.txt が置かれていること
  • サイトマップを送り直し、主要な記事のインデックス登録の状況を Search Console で確かめること

検索での表示回数や順位がどう動くかは、移行から1〜2か月ほど様子を見てから、この記事に追記します。

作り直すかどうかの判断は、別に考える

この記事は「移す」と決めたあとの手順です。そもそも全体を作り替えるのか、今のサイトに手を入れれば済むのかは、技術より先に決めることです。株式会社bundlyzeでは、その作り替えか手直しかの判断基準を会社のコラムで公開しています。bundlyzeの会社サイトも、このブログと同じく静的な配信とサーバーレスの関数で動かしています。

よくある質問

Q. WordPress から静的サイトに移すと、検索順位は下がりますか? A. URLと中身が同じなら、評価は引き継がれやすいと考えています。逆に、URLが変わったページに転送が無いと、そのページの評価は失われます。移行の前後で同じURLが開けることを、機械で全件確かめるのが一番の対策です。

Q. _redirects だけで、www なしから www への転送もできますか? A. _redirects はパスの転送を書く場所で、ホスト名の違いを条件にした転送は書けません。Cloudflare のリダイレクトルールか、この記事のように Pages Functions で書きます。私は *.pages.dev も同じ規則で寄せたかったので Functions にしました。

Q. ネームサーバーを変えるとき、サイトは止まりますか? A. 先に Cloudflare 側へ同じ DNS レコードを作っておけば、反映を待つ間も同じサイトが表示されるので止まりません。向き先を新サイトに変えるのは、ネームサーバーの移動が終わってから、別の段階で行うのが安全です。