@k8ordo/router

エラーと読み込み中の表示を出す

ページが描画中に例外を投げたときの表示と、ページの準備ができるまでの表示は、ルート表のオブジェクトに書きます。このページでは、errorとloadingの書き方と、React.lazyで分けたページの待ち方を説明します。

このページの内容

エラーを表示する

childrenを持つオブジェクトにerrorを書くと、その下のページが描画中に例外を投げたとき、ページの代わりにerrorのコンポーネントが描かれます。描かれる場所はレイアウトの<Outlet />の位置なので、レイアウトはそのまま残ります。

src/routes.ts
export const routes = defineRoutes({
  '/products': {
    layout: ProductsLayout,
    error: ProductsError,
    children: {
      '/': ProductList,
      '/:id': ProductPage,
    },
  },
});
src/products-error.tsx
import type { ErrorProps } from '@k8ordo/router';

export function ProductsError({ error, reset }: ErrorProps) {
  const message =
    error instanceof Error ? error.message : 'Something went wrong';

  return (
    <div role="alert">
      <p>{message}</p>
      <button onClick={reset} type="button">
        Try again
      </button>
    </div>
  );
}

errorのコンポーネントは、投げられた値をerrorで、描き直すための関数をresetで受け取ります。errorは何が投げられてもよいようにunknown型です。

reset()を呼ぶと、その場で下のページをもう一度描きます。また例外を投げれば、errorのコンポーネントに戻ります。

エラーの表示は、別のページに移ると消えます。消えるのは次のページが画面に出たときで、オブジェクトの下にあるレイアウトは作り直されず、状態を保ったままです。

ただし、クエリ文字列だけを変える状態の更新はページの切り替えではないので、エラーの表示は残ります。

情報

メモ

レイアウト自身が投げた例外は、同じオブジェクトのerrorでは受け止められません。errorのコンポーネントはレイアウトの内側に描かれるからです。その例外は外側のオブジェクトのerrorに届き、どこにも無ければ<Router>の外まで伝わります。

読み込み中の表示を出す

loadingには、その下のページがサスペンドしている間に出すコンポーネントを書きます。このコンポーネントはpropsを受け取りません。

src/routes.ts
export const routes = defineRoutes({
  '/products': {
    layout: ProductsLayout,
    error: ProductsError,
    loading: ProductsLoading,
    children: {
      '/': ProductList,
      '/:id': ProductPage,
    },
  },
});
src/products-loading.tsx
export function ProductsLoading() {
  return <p role="status">Loading products…</p>;
}

loadingを書いた位置には<Suspense>が置かれ、loadingのコンポーネントがそのfallbackになります。errorも書いたときは、この<Suspense>はエラーを受け止める位置より内側に入ります。

fallbackが出るのは、ページを最初に開いたときと、ほかのページからこのオブジェクトの下へ移ってきたときです。

すでにこのオブジェクトの下にいて、別のページへ移るときはfallbackを出しません。次のページの準備ができるまで、前のページを出したままにします。その間の待ちを見せたいときは、「いまいる場所を調べる」で説明するusePendingPathnameを使います。

ページのコードを分けて読み込む

ページのコンポーネントは、React.lazyで包んでからルート表に書いてもかまいません。そのページを初めて描くときに、コードが読み込まれます。

src/routes.ts
import { defineRoutes } from '@k8ordo/router';
import { lazy } from 'react';

const Settings = lazy(() => import('./pages/settings'));

export const routes = defineRoutes({
  '/': {
    layout: Shell,
    loading: PageLoading,
    children: {
      '/': Home,
      '/settings': Settings,
    },
  },
});

コードが届くまでの間は、上にある<Suspense>のfallbackが出ます。loadingを書くか、レイアウトの中で<Outlet />を<Suspense>で包んで、fallbackを出す場所を用意してください。

fallbackが出る場面はloadingと同じです。上の例では/のオブジェクトがすべてのページを包んでいるので、/settingsを直接開いたときはfallbackが出ます。一方で、ホームから/settingsへ移ったときは、コードが届くまでホームが出たままです。

警告

落とし穴

errorを書いたオブジェクトは、下のページをfallbackがnullの<Suspense>でも包みます。サーバーでの描画で例外を投げた部分を、ブラウザに任せるためです。そのため、その下でReact.lazyのページがサスペンドしても、上のレイアウトの<Suspense>までは届かず、何も表示されません。fallbackを出したいときは、同じオブジェクトにloadingを書くか、それより下に<Suspense>を置いてください。

k8ordo

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

© 2026 k8o — MIT License

組版:Noto Sans JP / M PLUS 2