@k8ordo/static

エラーとリダイレクト

何かが throw したときの error.tsx、何も一致しなかったときの not-found.tsx、移転した URL の redirect.ts。このモードでは、ビルド中に throw した Server Component はビルドを止め、not-found は 404.html になり、リダイレクトは訪問者を送り出すページとして書かれます。

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/routerErrorProps で、生成された表は各 error.tsxsatisfies 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.tsxshop/page.tsxshop/[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 したとき

ビルド中に Server Component が throw したページは、ページとして書かれません。訪問者のブラウザで描かれて初めてエラーが見える HTML を書く代わりに、ビルドが止まります。エラーはページの pathname と一緒に k8ordo: rendering /broken failed としてログに出ます。上に Suspense の境界(error.tsx もその 1 つ)があればビルドは次の行で終わり、無ければこの行ではなく React の本番用のエラー(An error occurred in the Server Components render…)で終わるので、throw されたメッセージが出るのは上のログの行だけです。

static build could not render /broken see the error above

クライアントコンポーネントは事情が違います。ビルドが HTML を作る途中でクライアントコンポーネントが throw しても、上に Suspense の境界(error.tsx もその 1 つ)があれば、その部分はブラウザに任されてファイルは書かれ、ブラウザが描き直します。そこでも throw すれば、hydration の後にいちばん近い error.tsx が出ます。上に Suspense の境界が無ければ、そのエラーでビルドが止まります。

つまりこのモードの error.tsx は、主にブラウザで起きる失敗のためにあります。hydration の後やクライアント遷移の後に、クライアントコンポーネントが throw した場合です。

error.tsx が受けないとき

クライアント遷移で届いたページが描画に失敗し、それを受ける error.tsx が無ければ、フレームワークは同じ URL を文書として読み込み直します。ネットワークの失敗や、ペイロードではない答え(ホストが配るファイルなど)も同じく文書の読み込みになります。hydration 中の失敗は読み込み直しません。すでに描かれた HTML を取り直しても良くはならず、繰り返すだけだからです。

このモードでは、読み込み直した文書はホストが配るそのページのファイルで、500 やエラーページで答えるサーバーはありません。サイトが持たない URL への答えも HTML の 404.html なので、同じく文書の読み込みになります。

not-found.tsx

not-found.tsx は、そのディレクトリ以下でほかのどれも答えなかった pathname に答える catch-all(/*)です。表では枝の最後に置かれるので、宣言されたルートがかならず先に試されます。ページと同じく paramspathname を受け取り、自分の <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.tsx404.html という 1 枚のファイルに描かれます。静的ホスティングは知らない URL すべてに 1 つのファイルで答えるので、表せる not-found.tsx は 1 つだけです。置く場所はロケールの区間の下でも構いません。2 つ以上宣言するとビルドが止まり、1 つも無ければ 404.html は書かれず、知らない URL への答えはホスティング次第になります。

a static host answers every unknown URL from one file, so only one not-found.tsx can be represented this table declares /docs/*, /*

404.html がどう描かれ、そこでのパラメータをどう扱うかは、リンク先にあります。 ビルドと配信

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.tsxredirect.ts を両方置くとビルドが拒みます。リダイレクトも宣言された URL として数えられるので、別のグループが同じ URL にページを置くことも拒まれます。

このモードでは、ステータスを送るサーバーはいないので、リダイレクトは訪問者を送り出すページとして書かれます(<meta http-equiv="refresh"> とリンク)。横に index.rsc は無いので、クライアント遷移はそこで URL をブラウザに渡し、ブラウザがそのページを読み込んで従います。permanent は書かれるファイルを変えません。リダイレクトは sitemap.xml に載らず、パラメータの下の redirect.ts には paths で値を渡します。