/archive にアクセスすると最新年の年間ふりかえりと同じ内容を表示するようにした

背景

直前の対応で、月別統計を表示するアーカイブページを /archive から /archive/timeline へ移動しました。これにより /archive というパスが空いたため、今回は年ごとのカテゴリー別まとめを表示する年間ふりかえりページ(/archive/年)への入り口として使うことにしました。

要件は「/archive にアクセスしたら、データが存在する最新年(例: /archive/2026)と同じ内容を見せる」ことでした。ここで重要なのは、ブラウザのURLは /archive のまま変えたくないという点です。単純にリダイレクトすると、アドレスバーの表示が /archive/2026 に変わってしまい、ブックマークや共有時のURLが年ごとに変化してしまいます。

リダイレクトではなくrewriteを使う

最初はAstroAstro.redirect() で実装しましたが、これは302ステータスでブラウザに新しいURLへの移動を指示するため、アドレスバーの表示が /archive/2026 に変わってしまいます。今回の要件は「同じ内容を表示したいだけで、URLは変えたくない」だったため、代わりに Astro.rewrite() を使いました。

---
export const prerender = false;

const { runtime } = Astro.locals;
const db = runtime.env.DB;

let latestYear = new Date().getFullYear();
try {
  const row = await db.prepare(`
    SELECT MAX(CAST(SUBSTR(createdAt, 1, 4) AS INTEGER)) AS yr
    FROM posts
    WHERE isDeleted IS NOT 1
  `).first();
  if (row?.yr) latestYear = row.yr;
} catch (_) {}

return Astro.rewrite(`/archive/${latestYear}`);
---

Astro.rewrite() はサーバー内部で別ルートの内容を描画してレスポンスとして返す仕組みで、ブラウザ側にはリダイレクトの指示を送りません。そのため、ユーザーからは常に /archive にアクセスしているように見えたまま、実際には最新年の年間ふりかえりページの内容が表示されます。

Astro.redirect()Astro.rewrite() の違いをまとめると以下のようになります。

URLの見た目 HTTPレスポンス
Astro.redirect() 遷移先のURLに変わる 3xxステータス+Locationヘッダー
Astro.rewrite() 元のURLのまま変わらない 200ステータスで遷移先の内容をそのまま返す

年をハードコードしない設計

もう一つのポイントは、表示する年を一切ハードコードしていないことです。投稿を保存するテーブルから作成日時の先頭4文字(年)を集計し、最大値を毎回取得しています。

そのため、来年以降に新しい年の投稿が増えていけば、コードを一切変更しなくても /archive は自動的にその年の年間ふりかえりの内容を表示するようになります。投稿データがまだ存在しない場合(開発環境の初期状態など)は、現在の暦年をフォールバック値として使う設計にしました。

既存の年間ふりかえりページとの関係

年間ふりかえりページ(/archive/年)自体は、前後の年へのナビゲーション矢印を投稿データの最小年・最大年から動的に算出する実装がすでに入っていました。今回追加した /archive の入り口は、この既存の仕組みに乗る形で「まずどの年の内容を見せるか」を決めるだけの薄い層として実装しています。

なお、年間ふりかえりページ内の前後年ナビゲーション(/archive/2025/archive/2027 など)は今まで通り明示的なURLへのリンクのままです。rewriteの対象は /archive(年を指定しないアクセス)のみで、個別の年のURLはそのまま独立したページとして機能します。

確認したポイント