コンテンツにスキップ
Vantage
日本語
Esc
navigateopen⌘Jpreview
このページの内容

ファイルベースルーティング

ファイルを置くだけでルートが生まれる。ページ・レイアウト・動的パラメータ・404/error の規約。

Vantage のルーティングは ファイルの配置がそのまま URL になります。ルーターの設定は書きません。

ページを置く場所

置き場所は 2 通りあり、src/ があるかどうかだけで決まります。設定ではなく検出です。

プロジェクト ページ走査のルート / になるファイル
src/ がある src/ の中だけ src/index.tsx
src/ が無い プロジェクトルート直下 index.tsx

src/ 自体は URL に現れません(src/sales/index.tsx/sales)。1 ファイルのアプリはそのまま 直下に置け、育ったら src/ を作ってまとめて移せます。

ページの規約

以下の「ファイル」列は、上のページ走査ルートからの相対パスです。

ファイル ルート
index.tsx /
monthly-analysis.tsx /monthly-analysis
sales/index.tsx /sales
sales/[customerId].tsx /sales/:customerId
sales/[...path].tsx /sales/*(catchall)
_layout.tsx そのディレクトリ配下のレイアウト(入れ子可)
_404.tsx not-found の UI(ルートのみ
_error.tsx error の UI(ルートのみ
  • [name] → 動的パラメータ、[...name] → catchall。
  • index は自身のディレクトリにマップ(名前は URL から落ちる)。
  • _ で始まるファイルは予約(_layout/_404/_error)またはプライベート(ルーティングされない)。

ルーティングされないディレクトリ

次のディレクトリは(どの階層でも)決して走査されません。ここには自由にコードを置けます。

components/   再利用する React コンポーネント
hooks/        カスタムフック
lib/          ユーティリティ
server/       API(存在するとサーバーが有効になる)
public/       静的アセット

動的パラメータの 3 つの綴り

同じパラメータが、場所によって 3 つの綴りで現れます。必ず一致させます。

場所 綴り
ファイル名 [id] sales/[customerId].tsx
表示ルート :id /sales/:customerId
リンク $id + params to="/sales/$customerId" params={{ customerId }}
import { Link, useParams } from "@squadbase/vantage/router";

function Row({ customerId }: { customerId: string }) {
  return (
    <Link to="/sales/$customerId" params={{ customerId }}>
      顧客を開く
    </Link>
  );
}

// sales/[customerId].tsx
export default function Customer() {
  const { customerId } = useParams({ strict: false });
  return <div>顧客 {customerId}</div>;
}

レイアウトと入れ子

_layout.tsx<Outlet /> を描画して子ルートを囲みます。ディレクトリごとに置けば入れ子になります。

// _layout.tsx
import { Outlet } from "@squadbase/vantage/router";

export default function RootLayout() {
  return (
    <div className="min-h-screen">
      <nav className="border-b p-4">My Dashboard</nav>
      <Outlet />
    </div>
  );
}

_404.tsx_error.tsxルート(プロジェクト直下)でのみ認識されます。

ルート一覧からナビを作る

useRoutes() はアプリの全ページルートを返します。ファイルシステムが正本なので、 ページファイルを 1 つ足せばナビに 1 行増えます。手で管理するリンク配列は不要です。

// _layout.tsx
import { Link, Outlet, useCurrentRoute, useRoutes } from "@squadbase/vantage/router";

export default function RootLayout() {
  // 動的ルート(/sales/:customerId)は URL が 1 つに定まらないので外す
  const routes = useRoutes().filter((route) => !route.dynamic);
  const current = useCurrentRoute();

  return (
    <div>
      <nav>
        {routes.map((route) => (
          <Link key={route.path} to={route.to} aria-current={route.path === current?.path}>
            {route.label}
          </Link>
        ))}
      </nav>
      <Outlet />
    </div>
  );
}

並び順は Vantage のスキャン順(浅い順 → 静的が動的より先 → アルファベット順)で、 そのままナビに使える順序です。

各要素は RouteInfo です。

フィールド 内容
path string 表示パス。/sales/:customerId
to string Linkto に渡す形。/sales/$customerId
params string[] 動的パラメータ名。catch-all は _splat
dynamic boolean 動的パラメータを持つか
index boolean ディレクトリの index ルートか
label string navLabeltitlepath の順で決まる表示名
title / description / navLabel string | undefined definePage の値

useCurrentRoute() は今表示中のルートを返します(404 のときは undefined)。 上の例のような「現在地」判定のほか、パンくずやページ見出しに使えます。

URL にフィルタ状態を置く

ダッシュボードの絞り込みは URL に置くと、リロードで消えず、そのまま同僚に共有できます。 useSearchParamuseState と同じ形で search params を読み書きします。

import { useSearchParam } from "@squadbase/vantage/router";
import { SegmentedControl } from "@squadbase/vantage/components";

export default function Sales() {
  const [region, setRegion] = useSearchParam("region", "all");

  return <SegmentedControl options={REGIONS} value={region} onChange={setRegion} />;
}
  • 値は常に文字列です(?year=2024 は数値としてパースされますが、この hook が文字列に戻します)。
  • デフォルト値(上の例では "all")や null を書き込むと、キーは URL から取り除かれます。
  • 履歴は既定で replace(戻るボタンが埋まらない)。{ replace: false } で push に変えられます。

配列やオブジェクトなど JSON になる値は useSearchState を使います。関数更新も渡せます。

import { useSearchState } from "@squadbase/vantage/router";

const [segments, setSegments] = useSearchState<string[]>("segments", []);
setSegments((prev) => [...prev, "enterprise"]);

HMR とルート再生成

.tsx ページや server/api ファイルを追加・削除すると、開発サーバーがルートを再生成して フルリロードします。ページ本体を編集した場合は React Fast Refresh がその場で反映します。

次に読む

このページは役に立ちましたか?