エラーとリダイレクト
ページの例外と、どのルートにも一致しない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を呼ばなくても消えます。
捕捉の範囲
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()の応答が返ります。
例外のデモ
このサイトのsrc/routes/[locale]/error.tsxが、描画中に例外を起こすClient Componentを捕捉します。
試してみる
- 「描画中にエラーを起こす」を押すと、このページの本文が「問題が発生しました」に置き換わります。ヘッダーとサイドバーは残ります。
- 「再読み込み」を押すと
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.tsxexport 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.tsximport { 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.tsexport default '/products';src/routes/[locale]/legacy/redirect.tsexport 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モードでもビルドは通ります。