@k8ordo/framework

エラーとリダイレクト

ページの例外と、どのルートにも一致しないURL、移したURLを扱えるようになります。

このページの内容

error.tsx

src/routes/error.tsx
'use client';
描画中の例外を捕捉できるのはブラウザだけ

import type { ErrorProps } from '@k8ordo/framework';

export default function RouteError({ reset }: ErrorProps) {
失敗した部分をその場で再描画する
  return (
    <section role="alert">
      <p>Something went wrong.</p>
      <button onClick={reset} type="button">
        Try again
      </button>
    </section>
  );
}

レイアウトやページの横にerror.tsxを置くと、その下で例外が起きたときに代わりに描画されます。ヘッダーのようなレイアウトの部分は残ります。

error.tsxはClient Componentにします。propsは、例外の値のerrorと、その部分を再描画するresetの2つです。paramsは受け取りません。

別のページへ移ると、error.tsxの表示はresetを呼ばなくても消えます。

捕捉の範囲

text
src/routes/
  layout.tsx
  error.tsx
  page.tsx
  shop/
    layout.tsx
    error.tsx
    page.tsx
    [id]/
      page.tsx

例外を捕捉するのは、起きた場所から見ていちばん近い上のerror.tsxです。この構成では、shop/page.tsxとshop/[id]/page.tsxの例外をshop/error.tsxが捕捉します。

error.tsxは同じディレクトリのlayout.tsxの内側に置かれます。そのためshop/layout.tsx自身の例外は、ルートのerror.tsxが捕捉します。

捕捉するerror.tsxが無いときは、クライアント側の遷移で描画に失敗したページを、ブラウザが同じURLの文書として読み込み直します。staticモードではホストにあるそのページのファイルが、serverモードではserve()の応答が返ります。

Playground

例外のデモ

このサイトのsrc/routes/[locale]/error.tsxが、描画中に例外を起こすClient Componentを捕捉します。

試してみる

  1. 「描画中にエラーを起こす」を押すと、このページの本文が「問題が発生しました」に置き換わります。ヘッダーとサイドバーは残ります。
  2. 「再読み込み」を押すとresetが呼ばれ、このページがその場で再描画されます。

staticモードのビルド

staticモードでは、ビルド中にServer Componentで例外が起きたページはファイルに書かれません。エラーのメッセージがページのURLと一緒にログに出ます。ビルドは失敗したページをすべて挙げたstatic build could not render /broken — see the error aboveで終わります。上にerror.tsxがあっても同じです。

Client Componentは扱いが違います。上にSuspenseの境界があれば、その部分を空けたままファイルが書き出されます。ブラウザで描画したときに例外が起き、error.tsxが表示されます。error.tsxもSuspenseの境界の1つです。上に境界が無ければ、Server Componentと同じくビルドできません。

serverモードの描画

serverモードでは、サーバーでの描画の例外をerror.tsxで捕捉できません。Suspenseの境界の中で例外が起きた部分は、描画をブラウザに任せます。HTMLはレイアウトを含み、ページの部分を空けたまま送られます。

ブラウザで同じコンポーネントがもう一度例外になり、ハイドレーションの後にerror.tsxが表示されます。応答のステータスは200のままです。本番では、ブラウザに渡るエラーのメッセージはReactの汎用の文言に置き換わります。元のメッセージは、サーバーのログにk8ordo: rendering /broken failedとして出ます。

上にSuspenseの境界が1つも無い場所で例外が起きると、HTMLを作れません。serve()はそのリクエストに500で答えます。

not-found.tsx

src/routes/not-found.tsx
export default function NotFoundPage() {
  return (
    <>
      <title>Not found</title>
      <h1>This page does not exist</h1>
    </>
  );
}

not-found.tsxは、そのディレクトリより下でどのルートにも一致しなかったURLに応答します。ルート表ではそのディレクトリの最後に置かれ、宣言したルートが先に試されます。paramsとpathnameを受け取り、自分の<title>を描画します。paramsは文字列のままです。

staticモードでは、not-found.tsxは404.htmlという1つのファイルに書き出されます。静的ホスティングは、存在しないURLすべてに1つのファイルを返します。そのため置けるnot-found.tsxは1つだけです。ロケールの区間の下に置いても構いません。2つ以上置くと、a static host answers every unknown URL from one fileで始まるエラーになり、ビルドできません。1つも置かなければ404.htmlは書かれず、存在しないURLに何を返すかは静的ホスティング次第です。404.htmlがブラウザでどう描画されるかはデプロイを見てください。

serverモードでは、not-found.tsxは404のステータスで答えます。ディレクトリごとに置けるので、docs/not-found.tsxは/docsの下の存在しないURLに、docs/layout.tsxの内側で答えます。1つも置かなければ、404という見出しと1行の説明だけの最小のページが、ルートレイアウトの内側に404で描画されます。<html lang>とスタイルシートも残ります。

notFound()

src/routes/products/[id]/page.tsx
import { notFound } from '@k8ordo/framework';
import type { PageProps } from '@k8ordo/framework';

import { findProduct } from '../../../lib/catalog.server';

export default async function ProductPage({
  params,
}: PageProps<'/products/:id'>) {
  const product = await findProduct(params.id);
  if (product === undefined) notFound();
ここで例外になり、次の行は実行されない
  return <h1>{product.name}</h1>;
}

スキーマが決めるのはパラメータの形までです。そのデータがあるかどうかはページだけが知っているので、無いときはnotFound()を呼びます。ページの代わりに、いちばん近い上のnot-found.tsxが404として描画されます。描画される場所は、それより上のレイアウトの内側です。

staticモードでは、ページのどこでnotFound()を呼んでも404になります。ただしpathsで渡したURLのページがnotFound()を呼ぶと、そのURLを挙げたエラーになり、ビルドできません。サイトにあるはずのURLに、404のページを書くことになるからです。

fallback.tsxのシェルの中でブラウザがnotFound()を呼んだときは、404になりません。いちばん近いnot-found.tsxがその場に描かれ、ステータスは200のままです。詳しくはパラメータを見てください。

serverモードでは、ページ自身のコンポーネントが返るまで文書を送りません。ステータスは本文より先に送るため、notFound()が呼ばれるかを待ちます。クライアント側の遷移では、ページが返るのを待たずにストリーミングを始めます。そのページがnotFound()を呼ぶと、ブラウザが同じURLを文書として読み込み直し、サーバーが404を返します。

警告

落とし穴

serverモードで、ページが返って応答が始まった後に<Suspense>の下からnotFound()を呼んでも、404にはなりません。ほかの例外と同じく、いちばん近いerror.tsxが捕捉します。

redirect.ts

src/routes/old/redirect.ts
export default '/products';
src/routes/[locale]/legacy/redirect.ts
export default { to: '/:locale/new', permanent: true };
行き先はルートのパターン。`:locale`は一致したURLの値で埋まる

URLを移したときは、古いディレクトリにpage.tsxの代わりにredirect.tsを置きます。default exportは、行き先の文字列か{ to, permanent }です。同じディレクトリにpage.tsxとredirect.tsを両方置くと、"old" cannot both render page.tsx and redirect — keep oneというエラーになり、ビルドできません。

staticモードでは、リダイレクトは訪問者を行き先へ送るページとして書き出されます。中身は<meta http-equiv="refresh">とリンクを持つHTMLで、permanentを付けても変わりません。隣にindex.rscは書かれません。クライアント側の遷移では、ブラウザがそのページを読み込んでから行き先へ移ります。

serverモードでは、GETとHEADに307を返し、permanentなら308を返します。ほかのメソッドには405を返します。クライアント側の遷移の行き先がリダイレクトなら、ブラウザが文書として読み込み直して行き先へ移ります。Server Actionの最後に訪問者を別のページへ送るには、redirect()を使います。使い方はServer Actionを見てください。

情報

メモ

default exportの型は、@k8ordo/framework/serverのRedirectTargetです。export default '/products' satisfies RedirectTargetと書くと、その場で形を検査できます。import typeで読み込めば、@k8ordo/framework/serverを使えないstaticモードでもビルドは通ります。

k8ordo

Baselineに入った機能を、制限なく使うReactのライブラリ群。

© 2026 k8o — MIT License

組版:Noto Sans JP / M PLUS 2