Back to posts

Astroの404ページとリダイレクト設計:旧URLの受け止め方

このサイトのリニューアルでは、フレームワーク(Gatsby → Astro)とホスティング(Netlify → Cloudflare Pages)が同時に入れ替わります。こういう全面刷新で地味に困るのが URL の扱いです。記事のパス設計が変われば旧 URL はそのままでは開けなくなるし、何も用意しなければ、ブックマークや検索結果から来た人が素のエラーページに行き着きます。

やることは大きく2つだけです。引っ越し先が決まった URL はリダイレクトで受け止める。受け止め先がない URL のために、戻れる導線のある 404 ページを用意する。この記事では Astro + Cloudflare Pages の前提で、その設計と手順をまとめます。移行全体の洗い出しは NetlifyからCloudflare Pagesへ移すときのチェックリスト にまとめてあり、この記事はその中の URL まわりに絞ったものです。

404 ページは src/pages/404.astro を置くだけ

Astro では src/pages に 404.astro(または 404.md)を置くと、ビルドで 404.html が生成され、主要な配信サービスが「存在しないパスへのアクセス」に対してそれを使ってくれます。Cloudflare Pages でもこの 404.html が自動で返されます。特別なルーティング設定は要りません。

中身は凝らなくていい代わりに、「ページが見つかりません」で終わらせないことが大事です。このサイトでは次の3つを置く形にしました。

  • サイト内検索へのリンク(このサイトは Pagefind で検索を持っているので、探していた記事に自分でたどり着ける)
  • 人気記事・最近の記事へのリンク数本
  • トップページへのリンクと、ブログであることの短い説明

もう1点、404 ページは検索結果に出てほしいページではないので、扱いを揃えておきます。

ポイント: レイアウトが <meta name="robots"> を出し分けできるなら 404 ページには noindex を付ける。あわせて @astrojs/sitemap はデフォルトで全ページをサイトマップに含むので、ビルド成果物の sitemap-0.xml に /404/ が混ざっていないか確認し、入っていたら filter オプションで除外する。サイトマップに載せるのは「インデックスしてほしい URL」だけです。

リダイレクトの置き場は2つある

Astro サイトでリダイレクトを書く場所は、主に次の2つです。似たような記法ですが、返ってくるものが根本的に違います。

置き場実体ステータスコード向いている用途
astro.config の redirects静的ビルドでは meta refresh の HTML ファイルを旧パスに生成静的ビルドでは効かない(HTML への埋め込みになるため)ホスト非依存にしたい場合、動的ルート同士の対応付け
Cloudflare Pages の _redirects ファイル配信側(エッジ)で処理されるルール301 / 302 などをそのまま返せる本番の旧 URL 受け止め全般

astro.config の redirects は Astro 2.9 からある公式の仕組みで、書き方は素直です。

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  redirects: {
    "/old-page": "/new-page",
    "/blog/[...slug]": "/articles/[...slug]",
  },
});

ただし静的ビルド(アダプターなし)の場合、Astro は旧パスに <meta http-equiv="refresh"> を含んだ HTML を出力します。ブラウザは転送してくれますが、HTTP の 301 ではありません。ステータスを指定しても、HTML ビルドではサーバー側で使われません。

一方、配信が Cloudflare Pages と決まっているなら、_redirects ファイルに書けばエッジで本物のステータスコードが返ります。Astro では public/_redirects に置けば、ビルド成果物のルートにそのままコピーされます。

このサイトの結論: 本番の受け止めは _redirects に寄せました。理由は単純で、旧 URL の移転であることを検索エンジンに伝えるには 301 が返る方が素直だからです。astro.config 側には書かないことで、「どっちが効いているのか分からない」状態も避けられます。

_redirects の書き方と、301 / 302 の判断

_redirects は1行に1ルール、[転送元] [転送先] [ステータスコード] の形式で書きます。

# public/_redirects(例)
/blog/2020/hello-world/  /posts/hello-world/  301
/blog/*                  /posts/:splat        301
/posts/:year/:slug       /articles/:year/:slug 301
/temp-campaign           /campaign/           302

ここで一番の落とし穴が、ステータスコードを省略するとデフォルトで 302 になることです。公式ドキュメントにも明記されていますが、「書かなければ 301 だろう」と思い込むと、恒久移転のつもりが一時転送として処理され続けます。移転系のルールでは必ず 301 を明記する、と決めておくのが安全です。

使い分けの判断は難しくありません。

  • 301(恒久移転): URL 体系の変更、記事の統合・移動など「もう戻らない」変更。検索エンジンも旧 URL の評価を新 URL へ引き継ぐ対象として扱います
  • 302(一時転送): キャンペーンやメンテナンスなど、期間が終われば元に戻す前提のもの。恒久移転に使うと評価の引き継ぎが曖昧になるので、迷ったら「戻す予定が具体的にあるか」で判断します

なお _redirects の上限は、静的ルール 2,000 件+動的ルール(ワイルドカード等)100 件で、1行あたり 1,000 文字までです。個人ブログの移行ならまず余りますが、件数が膨らむ場合は Cloudflare の Bulk Redirects が次の選択肢になります。

旧 URL の棚卸しは「アクセスされていた URL」から

リダイレクトを書き始める前に、そもそも旧サイトにどんな URL があったかを洗います。ソースコードから生成される URL 一覧だけだと、過去に公開して今は消えたページを取りこぼします。棚卸しは次の順で進めました。

  • 旧サイトの sitemap.xml を取得し、公開されていた URL の一覧にする
  • Google Search Console の「ページ」レポートで、インデックス済み・クロール済みの URL を書き出す
  • アクセス解析のランディングページレポートを見て、実際に流入のあった URL を拾う(インデックスされていなくても、外部からリンクされていることがある)
  • 一覧の各 URL について「新サイトの対応ページ」「統合先」「対応なし(404 で受ける)」のどれかを決める
  • 対応ページがあるものだけを _redirects に落とし、対応なしは無理にトップへ飛ばさず 404 で受ける

最後の点は大事で、関係のないページへ一律に飛ばす「とりあえずトップへリダイレクト」は、ソフト 404 として検索エンジンに扱われることがあります。対応先が本当にない URL は、正直に 404 を返す方が設計としてきれいです。

事故パターンを先に潰す

リダイレクトまわりで起きる事故は、だいたい型が決まっています。

チェーン(多段リダイレクト)。 A → B → C のように転送が連なると、表示が遅くなるうえ、クローラが途中で追跡をやめることがあります。移行のたびにルールを足していくと起きやすいので、転送先は必ず「最終形の URL」を指すよう、棚卸し表の時点で正規化しておきます。

ループ。 A → B と B → A が両方ある、あるいは転送先が自分自身を指していて、ブラウザが「リダイレクトが多すぎます」で止まります。_redirects は上から順に評価され、同じ転送元が複数あれば一番上が効きます。ルールを足すときは既存行との重なりを検索してからにします。

注意(Cloudflare Pages の仕様): _redirects のルールは、転送元パスに実ファイルが存在していても適用されます(ドキュメントに明記)。つまり、現行ページのパスと重なるルールを残すと、そのページ自体が開けなくなります。移行が済んだ古いルールの掃除も運用に含めておくべきです。

末尾スラッシュの不一致。 /posts/hello と /posts/hello/ は別パスとして扱われます。サイトの URL 方針(このサイトは末尾スラッシュありで統一)とルールの表記がずれていると、「書いたのに効かない」が起きます。代表パターンで実際にアクセスして確認するのが早道です。

公開後の確認リスト

デプロイしたら、代表的な URL で実際に挙動を確認します(移行後に見る項目全体は ホスティング移行後の確認リスト にまとめています)。ここは手を抜くと、ルールのタイプミスに何週間も気づきません。

  • 旧 URL を数本選び、curl -I でステータスが 301 で返り、location が最終 URL を指しているか確認する
  • ワイルドカード(:splat、プレースホルダー)のルールが、想定どおり部分パスを引き継いでいるか確認する
  • 存在しない適当なパスにアクセスし、404 ページが 404 ステータスで返り、導線リンクが生きているか確認する
  • 転送先がさらに転送されていないか(チェーン化していないか)を、転送先 URL 単体でも叩いて確認する
  • Search Console に新しいサイトマップを送信し、しばらく後に「リダイレクトエラー」や 404 の急増が出ていないか見る

まとめ

404 とリダイレクトは、リニューアルの「ついで作業」に見えて、旧サイトの資産をどれだけ新サイトに持ち越せるかを決める部分です。やること自体は、棚卸しして対応表を作り、_redirects に 301 で落とし、404 に戻れる導線を置く、という地味な作業の積み重ねです。フレームワークの移行そのものより目立ちませんが、公開直後に差が出るのはだいたいこちら側でした。

参考