エラーとリダイレクト
何かが throw したときの error.tsx、何も一致しなかったときの not-found.tsx、移転した URL の redirect.ts。このモードでは、知らない URL は本物の 404 になり、リダイレクトは 307 か 308 で答え、サーバーで throw したページも枠を持ったまま届いて、error.tsx をブラウザに任せます。
error.tsx
レイアウト(またはページ)の横に置いた error.tsx は、その下が throw したときに代わりに表示されます。描かれるのはレイアウトの内側なので、失敗しても枠はそのまま残ります。描画中のエラーを捕まえられるのはブラウザだけなので、error.tsx はクライアントコンポーネントです。
// src/routes/error.tsx
'use client';
import type { ErrorProps } from '@k8ordo/router';
export default function RouteError({ error, reset }: ErrorProps) {
return (
<section role="alert">
<p>{error instanceof Error ? error.message : 'something went wrong'}</p>
<button onClick={reset} type="button">
try again
</button>
</section>
);
}受け取るのは error(throw されたもの)と reset(その部分木をその場で描き直す関数)だけで、params は受け取りません。型は @k8ordo/router の ErrorProps で、生成された表は各 error.tsx を satisfies ErrorComponent で検査します。文言が URL に依存するなら、コンポーネント自身が URL から読みます。
どこまでを受け持つか
error.tsx の境界は、同じディレクトリのレイアウトの内側に置かれます。受け持つのは横の page.tsx と、下のディレクトリにあるものすべて(そのレイアウトを含む)です。横のレイアウト自身が throw したときは、1 つ上の error.tsx が受けます。throw した場所の上でいちばん近い error.tsx が答えます。
src/routes/
layout.tsx
error.tsx
page.tsx
shop/
layout.tsx
error.tsx
page.tsx
[id]/
page.tsxこの木では、shop/error.tsx が shop/page.tsx と shop/[id]/page.tsx の失敗を shop/layout.tsx の内側で受け、shop/layout.tsx 自身の失敗はルートの error.tsx がルートレイアウトの内側で受けます。ルートの page.tsx の失敗も、ルートの error.tsx が受けます。
このサイトには 2 つあります。src/routes/[locale]/error.tsx はページが throw したとき、ヘッダーとフッターを残したまま中身の位置に描かれます。src/routes/error.tsx は、[locale] のレイアウト自身が throw したときの、枠の無い全画面の受け皿です。
reset はその部分木をその場で描き直します。別のページへ移動すれば、失敗は何もしなくても消えます。境界は、ページを画面に出したナビゲーションごとに作り直されるからです。
触って確かめる
下のボタンは、描画中に throw するクライアントコンポーネントを出します。このページの中身は src/routes/[locale]/error.tsx に置き換わり、ヘッダーとフッターはそのまま残ります。「再読み込み」を押すと reset が呼ばれ、このページがその場で描き直されます。
サーバーでの描画中に throw したとき
サーバーでの描画にはエラー境界がありません。あるのは「Suspense の境界の中で throw した部分木はブラウザに描かせる」という規則で、error.tsx の境界はその Suspense の境界を兼ねています。そのため HTML は枠を持ち、ページのあった位置を空けたまま届きます。ブラウザが同じ位置で throw し、hydration の後に error.tsx が出ます。応答のステータスは 200 のままです。
本番では、ブラウザが受け取るエラーのメッセージは React の汎用の文言に置き換わり、throw されたメッセージはサーバーのログ(k8ordo: rendering /broken failed)にだけ出ます。フレームワークは digest を設定しないので、error.digest は空文字列です。訪問者が読む文言は error.tsx 自身が持ちます。
上に Suspense の境界(error.tsx もその 1 つ)が無い場所で throw すると、その部分を空けておく場所が無いので HTML を作れず、serve() はそのリクエストに 500 で答えます。
error.tsx が受けないとき
クライアント遷移で届いたページが描画に失敗し、それを受ける error.tsx が無ければ、フレームワークは同じ URL を文書として読み込み直します。ネットワークの失敗や、ペイロードではない答え(ホストが配るファイルなど)も同じく文書の読み込みになります。hydration 中の失敗は読み込み直しません。すでに描かれた HTML を取り直しても良くはならず、繰り返すだけだからです。
このモードでは、読み込み直した文書は serve() 自身の答えで、描けないページなら 500 です。アプリが持たない URL は文書の読み込みになりません。ハンドラが not-found のペイロードを 404 で返し、その場で描かれます。
not-found.tsx
not-found.tsx は、そのディレクトリ以下でほかのどれも答えなかった pathname に答える catch-all(/*)です。表では枝の最後に置かれるので、宣言されたルートがかならず先に試されます。ページと同じく params と pathname を受け取り、自分の <title> を描きます。
// src/routes/not-found.tsx
export default function NotFoundPage() {
return (
<>
<title>Not found</title>
<h1>This page does not exist</h1>
</>
);
}catch-all の上にあるパラメータは検証されないので、/:locale/* の not-found.tsx が受け取る params.locale は、どんな文字列でもありえます。使う前に確かめます。
このモードでは、not-found.tsx の答えは本物の 404 ステータスを持ちます。ディレクトリごとに置けるので、docs/not-found.tsx は /docs の下の知らない URL を docs/layout.tsx の内側で描けます。1 つも宣言しなければ、表にない pathname には 404 という見出しと 1 行だけの最小のページが 404 で返ります。
redirect.ts
移転したディレクトリには、page.tsx の代わりに redirect.ts を置きます。default export は行き先の文字列か、{ to, permanent } です。
// src/routes/old/redirect.ts
export default '/products';// src/routes/[locale]/legacy/redirect.ts
export default { to: '/:locale/new', permanent: true };行き先はパターンで、一致したパラメータで埋められます。/:locale/legacy は /:locale/new へ送れます。自分のパターンに無いパラメータを行き先で名指すと、そのリダイレクトに答える時点で失敗します。
リダイレクトは表より先に調べられます。リダイレクトするディレクトリには描くページが無いので、同じディレクトリに page.tsx と redirect.ts を両方置くとビルドが拒みます。リダイレクトも宣言された URL として数えられるので、別のグループが同じ URL にページを置くことも拒まれます。
このモードでは、答えは 307、permanent なら 308 で、location ヘッダーが行き先です。クライアント遷移がリダイレクトに当たると、ペイロードではなく HTML の答えが返るので URL をブラウザに渡し、ブラウザが文書の読み込みとしてリダイレクトに従います。アドレスバーは正しい URL になります。default export の形は RedirectTarget として export されています。
Server Action から訪問者を送るのは redirect() で、リンク先で説明します。 アクションとリクエスト
アプリが答えるステータス
ページはステータスを決めません。ステータスは、どのルートがどう答えたかで決まります。
| いつ | ステータス |
|---|---|
| ページが描かれた | 200 |
Server Component が error.tsx の下で throw した(その部分はブラウザに任される) | 200 |
| 表にない pathname、またはスキーマが拒んだ値 | 404 |
redirect.ts(permanent なら 308) | 307 |
JavaScript なしで送られたフォームのアクションが redirect() した | 303 |
Origin ヘッダーが無いか、そのホストが一致しない POST | 403 |
ハンドラが答えを作れなかった(serve() の場合) | 500 |