# ルートを定義する

ここでは、ファイルシステムベースのルーターの基本について学習します。ルートの定義方法、パラメータとページレイアウトの使用方法を学習します。

## 設定

ルーターは、プロジェクトのルートに`react-server.config.mjs`または`react-server.config.json`ファイルを作成することで設定できます。設定ファイルは設定オブジェクトをエクスポートし、ルートの`root`パスを含める必要があります。静的ファイルの`public`パスを指定することもできます。ファイルシステムベースのルーターは、ルートディレクトリ内のファイルを自動的にスキャフォールドし、ルートディレクトリ内のファイルに基づいてルーティングを準備および構築します。

```js
export default {
  root: "src/pages",
  public: "public",
};
```

## ルートの作成

ディレクトリとファイルは、アプリケーションが使用するルートを指定します。ルーターには、ディレクトリとファイルの名前をルートパスとルートパラメータに変換する規則が組み込まれています。新しいルートを作成するには、ルートディレクトリにファイルを作成する必要があります。

ルートファイルは、ルートをレンダリングするために使用されるReactコンポーネントをデフォルトでエクスポートする必要があります。デフォルトではページはサーバーコンポーネントです。ページの先頭に`"use client"`ディレクティブがある場合、[クライアント専用ルート](/router/typed-router#client-only-routes)になります — ナビゲーションはサーバーラウンドトリップなしで完全にクライアント上で行われ、コンポーネントの状態はナビゲーション間で保持されます。

```jsx
export default function Home() {
  return <h1>Home</h1>;
}
```

### インデックスルート

ファイルに`index`または`page`という名前を付けると、インデックスルートとして扱われます。インデックスルートは、ディレクトリのデフォルトルートです。たとえば、ルートディレクトリに`index.jsx`という名前のファイルを作成すると、アプリケーションのデフォルトルートとして扱われます。`about` という名前のディレクトリに`index.jsx` という名前のファイルを作成すると、`about`ディレクトリのデフォルトルートとして扱われます。

### 名前付きルート

ファイルに`index`または`page`以外の名前を付けると、名前付きルートとして扱われます。たとえば、ルートディレクトリに`about.jsx`という名前のファイルを作成すると、パス`/about`を持つ名前付きルートとして扱われます。`users`という名前のディレクトリに`about.jsx`という名前のファイルを作成すると、パス`/users/about`を持つ名前付きルートとして扱われます。

### ネストされたルート

ルートディレクトリにさらにディレクトリを作成することで、ネストされたルートを作成できます。たとえば、ルートディレクトリに`users`という名前のディレクトリを作成し、`users`ディレクトリに`index.jsx`という名前のファイルを作成すると、パス`/users`を持つネストされたルートとして扱われます。`users`ディレクトリに`about.jsx`という名前のファイルを作成すると、パス`/users/about`を持つネストされたルートとして扱われます。

### ルートパラメータ

ファイル名に括弧で囲んだパラメータ名を追加することで、ルートパラメータを作成できます。たとえば、ルートディレクトリに`[id].jsx`という名前のファイルを作成すると、パス`/[id]`を持つ名前付きルートとして扱われます。`users`というディレクトリに`[id].jsx`という名前のファイルを作成すると、パス`/users/[id]`を持つ名前付きルートとして扱われます。ネストされたディレクトリを作成することで、ネストされたルートパラメータを作成することもできます。たとえば、ルートディレクトリに`users`というディレクトリを作成し、`users`ディレクトリに`[id].jsx`という名前のファイルを作成すると、パス`/users/[id]`を持つネストされたルートとして扱われます。コンポーネント内のルートパラメータにReact propsとしてアクセスできるようになります。

```jsx
export default function User({ id }) {
  return <h1>User #{id}</h1>;
}
```

単一のルートセグメントで複数のルートパラメータを使用することもできます。たとえば`[id]-[name].jsx`という名前のファイルを作成すると、`/[id]-[name]`というパスパターンを持つルートとして扱われ、コンポーネントで`id`と`name`の両方がプロパティとして受け取られます。

```jsx
export default function User({ id, name }) {
  return <h1>User #{id} - {name}</h1>;
}
```

> **マッチャーエイリアス:** パラメータブラケットには`=alias`サフィックスを付けることができます。たとえば`[id=numeric].page.jsx` → `/[id=numeric]`。エイリアスは`export const matchers`で指定した述語にセグメントを結び付け、ルートがマッチする前にセグメントの形状でゲートをかけることができます。後述の[ルートマッチャー](/router/define-routes#route-matchers)を参照してください。プレーンな`[id]`はフィルタリングなしでパラメータを抽出します。

### 複数セグメントのルートパラメータ

ファイル名に括弧で囲んだパラメータ名を追加することで、複数セグメントのルートパラメータを作成できます。たとえば、ルートディレクトリに`[...id].jsx`という名前のファイルを作成すると、パス`/[...id]`を持つ名前付きルートとして扱われます。`users` という名前のディレクトリに`[...id].jsx`という名前のファイルを作成すると、パス`/users/[...id]`を持つ名前付きルートとして扱われます。ネストされたディレクトリを作成することで、ネストされたルートパラメータを作成することもできます。たとえば、ルートディレクトリに`users`という名前のディレクトリを作成し、`users`ディレクトリに`[...id].jsx`という名前のファイルを作成すると、パス`/users/[...id]`を持つネストされたルートとして扱われます。

実行時のパラメータは文字列の配列になります。コンポーネント内のルートパラメータにReactプロパティとしてアクセスできるようになります。

```jsx
// /[...slug].page.jsx
export default function Page({ slug }) {
  return <h1>/{slug.join("/")}</h1>;
}
```

### オプションセグメントのルートパラメータ

ファイル名に括弧で囲んだパラメータ名を追加することで、オプションセグメントのルートパラメータを作成できます。たとえば、ルートディレクトリに`[[...id]].jsx`という名前のファイルを作成すると、パス`/[[...id]]`を持つ名前付きルートとして扱われます。`users`という名前のディレクトリに`[[...id]].jsx`という名前のファイルを作成すると、パス`/users/[[...id]]`を持つ名前付きルートとして扱われます。ネストされたディレクトリを作成することで、ネストされたルートパラメータを作成することもできます。たとえば、ルートディレクトリに`users`という名前のディレクトリを作成し、`users`ディレクトリに`[[...id]].jsx`という名前のファイルを作成すると、パス`/users/[[...id]]`を持つネストされたルートとして扱われます。

> **省略:** ディレクトリ名またはファイル名の一部を括弧で囲むことで省略できます。たとえば、ルートディレクトリに`(404).[[...slug]].page.mdx`という名前のファイルを作成すると、パス`/[[...slug]]`を持つルートとして扱われます。これを使用することで、ルートパスに影響を与えずディレクトリ名またはファイル名に情報を追加できます。

## レイアウト

ファイル名に`layout.jsx`を含むファイルを作成することで、レイアウトを作成できます。レイアウトファイルは、レイアウトファイルと同じディレクトリ内のすべてのルートをラップするために使用されます。サブディレクトリに`layout.jsx`という名前のファイルを作成することで、ネストされたレイアウトを作成することもできます。レイアウトファイル名に省略された部分を使用することもできます。たとえば、ルートディレクトリに`(root).layout.jsx`という名前のファイルを作成すると、ルートディレクトリ内のすべてのルートのレイアウトとして使用されます。

レイアウトコンポーネントは、ルートコンポーネントをレンダリングするために使用する必要がある`children`プロパティを受け取ります。

```jsx
export default function Layout({ children }) {
  return (
    <>
      <h1>Layout</h1>
      {children}
    </>
  );
}
```

## 透過ルートセグメント

透過セグメントは、URLにレンダリングされないセグメントですが、ファイルを自分で識別するために使用されます。透過セグメントは`(transparent).page.jsx`という名前のファイルを作成することで使用できます。ここで、`(transparent)`は透過セグメントの名前で、任意の名前にすることができます。たとえば、ルートディレクトリに`(main).page.jsx`という名前のファイルを作成すると、パス`/`のルートとして扱われます。`users`という名前のディレクトリに`(main).page.jsx`という名前のファイルを作成すると、パス`/users`のルートとして扱われます。また、ディレクトリ構造で透過セグメントを使用して、ファイルをグループ化することもできます。たとえば、`(dashboard)/users`という名前のディレクトリに`page.jsx`という名前のファイルを作成すると、パス`/users`のルートとして扱われます。

```txt
src
- (root).layout.jsx
- (root).page.jsx
- (dashboard)
  - users
    - (users).page.jsx
    - [userId].page.jsx
```

## エスケープルートセグメント

ルートセグメントを中括弧で囲むことで、ルートセグメントをエスケープできます。たとえば、ルートディレクトリに`{sitemap.xml}.server.mjs`という名前のファイルを作成すると、パス`/sitemap.xml`を持つ名前付きルートとして扱われます。

> **例:** ファイルシステムベースのルーティングの基本的な例については、[examples](https://github.com/lazarv/react-server/tree/main/examples/photos) ディレクトリのPhotosの例を参照してください。

## ルートバリデーション

ファイルシステムベースのルーターでは、ページファイルから`validate`オブジェクトをエクスポートすることで、ルートパラメータと検索パラメータをランタイムでバリデーションできます。ランタイムがこのエクスポートを自動的に検出します — 追加の設定は不要です。

`validate`エクスポートは、オプションの`params`と`search`キーを持つオブジェクトで、各キーには任意の互換ライブラリ（Zod、ArkType、Valibot、または[`ValidateSchema`](/router/typed-router#validation)インターフェースを満たすもの）のスキーマを設定します。

```tsx filename="pages/user/[id].page.tsx"
import { z } from "zod";
import { user } from "@lazarv/react-server/routes";

export const validate = {
  params: z.object({
    id: z.string().regex(/^\d+$/, "ID must be numeric"),
  }),
};

export default user.createPage(({ id }) => {
  // idはstring型で、/^\d+$/にマッチすることが保証される
  return <h1>User {id}</h1>;
});
```

バリデーションが存在する場合、生成されたTypeScript型はスキーマからパラメータと検索型を推論します — そのため`useParams()`、`.Link`、`createPage`はすべて生の文字列ではなくバリデーション済みの型を受け取ります。

**ArkTypeの場合：**

```tsx filename="pages/user/[id].page.tsx"
import { type } from "arktype";
import { user } from "@lazarv/react-server/routes";

export const validate = {
  params: type({ id: /^\d+$/ }),
};

export default user.createPage(({ id }) => {
  return <h1>User {id}</h1>;
});
```

**検索パラメータのバリデーション：**

```tsx filename="pages/products.page.tsx"
import { z } from "zod";
import { products } from "@lazarv/react-server/routes";

export const validate = {
  search: z.object({
    sort: z.enum(["name", "price", "rating"]).catch("name"),
    page: z.coerce.number().int().positive().catch(1),
  }),
};

export default products.createPage(() => {
  const { sort, page } = products.useSearchParams();
  return <div>Sorted by {sort}, page {page}</div>;
});
```

バリデーションが失敗した場合（例：上記の数値バリデーションで`/user/abc`）、`useParams()`は`null`を返し、`useSearchParams()`は空のオブジェクト`{}`を返します。これにより、コンポーネントでエラーを処理したり、フォールバックルートに遷移させたりできます。

## ルートマッチャー

マッチャーは、**URL がそもそもルートにマッチするかどうか**をゲートする述語です。ルーティング時にページコードが読み込まれる前に実行され、同じパラメータ空間を形状によって分割する兄弟ルートを定義できます。

マッチャー構文を使用するには、ファイル名のパラメータブラケット内に`=alias`サフィックスを追加します。エイリアスは`export const matchers`で使用するキーになります。

| ブラケット形式 | 例 | マッチャーが受け取る値 |
|---------------|-----|----------------------|
| `[name=alias]` | `[id=numeric].page.tsx` | `string` |
| `[[name=alias]]` | `[[tab=known]].page.tsx` | `string` |
| `[...name=alias]` | `[...slug=nested].page.tsx` | `string[]` |
| `[[...name=alias]]` | `[[...path=valid]].page.tsx` | `string[]` |

ページ（またはミドルウェアや API ルート）から`matchers`オブジェクトをエクスポートします。キーはパスで使用しているエイリアスと一致させます。各値は URL を受け入れる場合は`true`、拒否する場合は`false`を返す述語です。拒否された URL は兄弟ルートにフォールスルーします — それ自体では 404 にはなりません。

```tsx filename="pages/product/[sku=uppercase].page.tsx"
import { productSkuUppercase } from "@lazarv/react-server/routes";

export const matchers = productSkuUppercase.createMatchers({
  uppercase: (value) => /^[A-Z0-9-]+$/.test(value),
});

export default productSkuUppercase.createPage(({ sku }) => {
  return <h1>Product {sku}</h1>;
});
```

その隣に兄弟の`[sku].page.tsx`を配置すると、拒否された値を受け止めます。

```tsx filename="pages/product/[sku].page.tsx"
import { product } from "@lazarv/react-server/routes";

export default product.createPage(({ sku }) => {
  return <h1>Fallback: {sku}</h1>;
});
```

生成されるルート名（`productSkuUppercase`と`product`）は、[ルート命名](/router/define-routes#routes-module-naming)で説明されている導出ルールから直接得られます。マッチャーでゲートされたセグメントはパラメータ名とエイリアスの両方を寄与させ、ベアなダイナミックセグメントは取り除かれます。明示的な`route`オーバーライドは不要です。

これで`/product/ABC-123`はマッチャーでゲートされたページをレンダリングし、`/product/abc-123`は兄弟にフォールスルーします。ランタイムはマッチャーでゲートされたルートを最初に試します — 同じパス形状でより具体的なルートは、常により具体的でない兄弟よりも上位にランクされます。

**キャッチオールマッチャーは配列を受け取ります。** エイリアスサフィックスは`[...slug]`や`[[...slug]]`でも同様に動作しますが、述語はセグメントの配列全体を受け取り、文字列は受け取りません。

```tsx filename="pages/docs/[...slug=nested].page.tsx"
import { docsSlugNested } from "@lazarv/react-server/routes";

export const matchers = docsSlugNested.createMatchers({
  nested: (slug) => slug.length >= 2,
});

export default docsSlugNested.createPage(({ slug }) => {
  return <article>{slug.join("/")}</article>;
});
```

ここで`/docs/intro`はフォールスルーし（長さ 1）、`/docs/guide/install`はマッチします。

**型付きエイリアス。** `createMatchers`に渡せるエイリアスは、型生成時にルートパスから抽出されます。`[id=numeric]`のようなパスは`{ numeric?: (value: string) => boolean }`を生成し、`[...slug=nested]`のようなパスは`{ nested?: (value: string[]) => boolean }`を生成します。パスに存在しないエイリアスを使用するのは型エラーです。

**マッチャーとバリデーションの違い。** これらは異なる問題を解決します。

- **マッチャー**は**ルーティング**をゲートします。拒否は「この URL はこのルート用ではない」という意味です — 兄弟ルートとフォールバックにチャンスが与えられます。ページコードは読み込まれません。
- **バリデーション**は**マッチしたルートのパラメータ**をゲートします。拒否は「このルートはマッチしたが、パラメータがスキーマを満たさない」という意味です — `useParams()`は`null`を返し、ページは依然としてレンダリングされ、エラーを処理する必要があります。

複数の兄弟ルートが同じパラメータスロットを共有しつつ、その形状が異なる場合はマッチャーを使用してください。単一のルートがレンダリング前に抽出済みのパラメータを制約する必要がある場合はバリデーションを使用してください。

**スコープ。** マッチャーはページ、アウトレットページ、ミドルウェア、および API ルートハンドラーで尊重されます。レイアウトとエラー/ローディング/フォールバックの境界はマッチャーディスパッチに参加しません — それらは包含関係とラップするページによって選択されます。

## ルートモジュール

ファイルシステムベースのルーターを使用すると、ランタイムは仮想的な`@lazarv/react-server/routes`モジュールを自動的に生成します。このモジュールはアプリケーション内のすべてのルートに対して名前付きルートディスクリプタをエクスポートします — ファイル構造から導出され、開発中はリアルタイムで更新され、各ビルドで再生成されます。

```tsx
import { index, about, user, dashboard } from "@lazarv/react-server/routes";
```

各エクスポートされたディスクリプタは、`createRoute`で作成されたものと同じ機能を持つ完全な[`RouteDescriptor`](/router/typed-router#api-route-descriptor)です：

- **`.Link`** — コンパイル時のパラメータチェック付き型付きLinkコンポーネント
- **`.href(params?)`** — 型付きパラメータからURLパス名を構築
- **`.useParams()`** — 型付きでバリデーション済みのパラメータを読み取る（サーバーとクライアントコンポーネントで動作）
- **`.useSearchParams()`** — 型付きでバリデーション済みの検索パラメータを読み取る
- **`.SearchParams`** — ルートスコープの検索パラメータ変換境界

さらに、ページコンポーネントの型付けのためのコンテキスト対応ヘルパー関数：

- **`.createPage(component)`** — バリデーション済みパラメータをプロップとする型付きページコンポーネント
- **`.createLayout(component)`** — `children`とブランド付きアウトレットプロップを持つ型付きレイアウト
- **`.createLoading(component)`** — 型付きローディングフォールバックコンポーネント
- **`.createError(component)`** — 型付きエラーバウンダリコンポーネント
- **`.createFallback(component)`** — 型付きフォールバックコンポーネント
- **`.createMiddleware(handler)`** — リクエストパラメータ付きの型付きミドルウェア
- **`.createMatchers(matchers)`** — 型付きマッチャーマップ（パスに`=alias`ブラケットを含むルートでのみ利用可能）。エイリアスはパスから抽出されます。キャッチオールエイリアスは`string[]`を、シングルエイリアスは`string`を受け取ります。[ルートマッチャー](/router/define-routes#route-matchers)を参照してください。

これらのヘルパーはランタイムではアイデンティティ関数です — コンポーネントをそのまま返します。ルートパス、`validate`エクスポート、アウトレット構造から正しいプロップ型を推論するためにTypeScript専用で存在します。

`@lazarv/react-server/routes`と並んで、ファイルルーター内のすべてのアウトレットに対して、型付きでサーバープリロードされるバインド済みの`ReactServerComponent`を公開する仮想モジュール`@lazarv/react-server/outlets`も生成されます。詳細は下の[型付きアウトレットコンポーネント](#routes-module-bound-outlets)を参照してください。

### ルート命名

ルート名はファイルパスからキャメルケースで自動的に導出されます：

| ファイルパス | エクスポート名 |
|-----------|--------------|
| `pages/page.tsx` or `pages/index.tsx` | `index` |
| `pages/about.tsx` | `about` |
| `pages/user/[id].page.tsx` | `user` |
| `pages/user/posts.page.tsx` | `userPosts` |
| `pages/dashboard/settings.page.tsx` | `dashboardSettings` |
| `pages/product/[sku=uppercase].page.tsx` | `productSkuUppercase` |
| `pages/docs/[...slug=nested].page.tsx` | `docsSlugNested` |

`[id]`のような動的セグメントは名前から除去されます。`pages/[id].page.tsx`のような純粋に動的なパスでは、パラメータ名（`id`）がルート名として使用されます。セグメントがマッチャーエイリアス（例：`[sku=uppercase]`）を持つ場合、パラメータ名とエイリアスの両方が導出名に寄与します — そのため、マッチャーでゲートされたページとそのベアな兄弟は、数値サフィックスなしで自動的に別々の名前を取得します。数値サフィックスは、導出が真の衝突を生み出した場合の最後の手段としてのみ適用されます。

ページファイルから`route`定数をエクスポートすることで、自動導出された名前をオーバーライドできます：

```tsx
export const route = "userProfile";
```

エクスポートされる名前は自動導出された名前の代わりに`userProfile`になります：

```tsx
import { userProfile } from "@lazarv/react-server/routes";
```

### 型付きページ

`createPage`を使用して、ルートのパスパラメータと`validate`エクスポートから推論された型付きプロップを取得します：

```tsx filename="pages/user/[id].page.tsx"
import { z } from "zod";
import { user } from "@lazarv/react-server/routes";

export const route = "user";

export const validate = {
  params: z.object({ id: z.string().regex(/^\d+$/, "ID must be numeric") }),
};

// TypeScriptが推論: ({ id: string }) => ReactNode
export default user.createPage(({ id }) => {
  return <h1>User {id}</h1>;
});
```

`validate`なしの場合、パラメータ型はパスパターンから来ます — `[id]`は`{ id: string }`、`[...slug]`は`{ slug: string[] }`を返します。`validate`ありの場合、型はスキーマの出力型から推論され、より狭い型になり得ます（例：正規表現制約付きの文字列、変換後の数値）。

動的パラメータのないページの場合、`createPage`は空のプロップオブジェクトを受け取ります：

```tsx filename="pages/about.tsx"
import { about } from "@lazarv/react-server/routes";

export default about.createPage(() => {
  return <h1>About</h1>;
});
```

### 型付きレイアウトとアウトレット

`createLayout`を使用して、型付き`children`とアウトレットプロップを取得します。アウトレットはレイアウトのディレクトリ配下の`@outletName`ディレクトリにファイルを配置して作成します：

```
pages/
├── dashboard/
│   ├── (dashboard).layout.tsx
│   ├── index.page.tsx
│   ├── @sidebar/
│   │   └── nav.page.tsx
│   └── @content/
│       ├── @content.default.tsx    # ルートがマッチしない時のデフォルトコンテンツ
│       └── feed.page.tsx
```

レイアウトは型付きアウトレットプロップを受け取ります — 各アウトレットはTypeScriptが個別に追跡するブランド付き`React.ReactElement`で、混在を防ぎます：

```tsx filename="pages/dashboard/(dashboard).layout.tsx"
import { dashboard } from "@lazarv/react-server/routes";

export default dashboard.createLayout(({ children, sidebar, content }) => {
  return (
    <div>
      <h1>Dashboard</h1>
      <div style={{ display: "grid", gridTemplateColumns: "200px 1fr" }}>
        <aside>{sidebar}</aside>
        <main>
          <div>{content}</div>
          <div>{children}</div>
        </main>
      </div>
    </div>
  );
});
```

**Nullableアウトレット：** アウトレットディレクトリに`@outletName.default.tsx`ファイルがある場合、アウトレットは常にレンダリングされます（non-nullable）。デフォルトがない場合、アウトレットは`ReactElement | null`になります — TypeScriptがnullチェックを強制します。

上の例では、`content`にはデフォルト（`@content.default.tsx`）があるため常に`ReactElement`です。`sidebar`にデフォルトがない場合、`ReactElement | null`として型付けされます。

### 型付きアウトレットコンポーネント

前のセクションでは*受け取り側* — 隣接する`@outletName/`ディレクトリからファイルルーターが埋めるアウトレットスロットを消費するレイアウト — を示しました。*送り出し側*にも独自の型付きエントリーポイントがあります：ファイルルーターで宣言されたアウトレットごとに1つの名前空間をエクスポートする、仮想的な`@lazarv/react-server/outlets`モジュールです。

```tsx
import { sidebar, content } from "@lazarv/react-server/outlets";

<sidebar.Outlet url="/dashboard/nav" />
<content.Outlet url="/dashboard/feed" />
```

各名前空間は、そのアウトレット名にバインドされた1つの`Outlet`コンポーネント（PascalCase、JSX呼び出し可能）を公開します。背後にある``形式と比較して、これは3つの利点をもたらします：

- **文字列指定のアウトレット名が不要。** アウトレット識別子はインポート時にバインドされます。タイポは起こり得ず、未知のアウトレット名はコンパイル時に失敗します。
- **型付きの`url`。** `url`プロップは`Link.to`と同じユニオン — ファイルルーター内のすべての静的・動的ルート — を受け入れます。タイポは拒否され、動的セグメントは既に具体化されている必要があります（例：`/user/42`であって`/user/[id]`ではない）。
- **ブランド付きの戻り型。** コンポーネントは`Outlet<"sidebar">`（またはその名前）を返すため、キャストなしで同名の`createLayout`スロットを満たせます。

デフォルトではアウトレットはサーバー側でプリロードされます：バインドされたコンポーネントがサーバーコンポーネント内でレンダリングされると、ランタイムは`url`をファイルルーターのマニフェストに対して解決し、対応する`@outletName/...page.tsx`（または`@outletName.default.tsx`フォールバック）を見つけ、それをレンダリングして結果を`children`として`ReactServerComponent`に渡します。そのため、SSRのHTMLには初回描画の時点でアウトレットコンテンツが含まれており、クライアントのラウンドトリップは不要です。

2つのオプトアウト：

- **`defer={true}`** — プリロードをスキップし、ハイドレーション後にクライアントがURLをフェッチするようにします。アウトレットの内容がブラウザ依存の状態に依存する場合や、親ページのレンダリングをアウトレット解決でブロックしたくない場合に有用です。
- **明示的な`children`** — 自分でchildrenを渡すと、プリロードは完全にバイパスされ、あなたのツリーがそのまま使われます。

```tsx filename="pages/panels.page.tsx"
import { sidebar, content } from "@lazarv/react-server/outlets";

export default function Panels() {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "200px 1fr" }}>
      <aside>
        {/* /dashboard/nav をサーバー側でサイドバースロットに解決します */}
        <sidebar.Outlet url="/dashboard/nav" />
      </aside>
      <main>
        {/* `defer` でハイドレーション後のクライアントフェッチに切り替えます */}
        <content.Outlet url="/dashboard/feed" defer />
      </main>
    </div>
  );
}
```

ランタイムは`.react-server`ディレクトリの`react-server-routes.d.ts`の隣に`react-server-outlets.d.ts`を書き出します。このファイルは`@lazarv/react-server/outlets`モジュールを宣言し、マニフェストで検出された各アウトレット名に対して1つの名前空間を持ちます。有効なJavaScript識別子ではないアウトレット名（例：ハイフン付きのディレクトリ名）はこのモジュールから除外されます — そのようなアウトレットは引き続き文字列指定の``形式で到達可能です。

### 型付きローディングとエラーコンポーネント

ローディングとエラーコンポーネントも同じパターンに従います：

```tsx filename="pages/user/[id].loading.tsx"
import { user } from "@lazarv/react-server/routes";

export default user.createLoading(() => {
  return <div>Loading user profile...</div>;
});
```

```tsx filename="pages/user/[id].error.tsx"
import { user } from "@lazarv/react-server/routes";

export default user.createError(({ error }) => {
  return <div>Error loading user: {error.message}</div>;
});
```

### Linkとhrefの使用

ルートモジュールからのルートディスクリプタは`createRoute`ディスクリプタと同様に動作します — ナビゲーションには`.Link`を、URL構築には`.href()`を使用します：

```tsx filename="pages/(root).layout.tsx"
import { index, about, user, dashboard } from "@lazarv/react-server/routes";

export default index.createLayout(({ children }) => {
  return (
    <html lang="en">
      <body>
        <nav>
          <index.Link>Home</index.Link>
          <about.Link>About</about.Link>
          <user.Link params={{ id: "42" }}>User 42</user.Link>
          <dashboard.Link>Dashboard</dashboard.Link>
        </nav>
        {children}
      </body>
    </html>
  );
});
```

動的セグメントのないルート（`index`、`about`など）は`params`を受け付けません。動的セグメントを持つルート（`user`など）は必須です — TypeScriptがコンパイル時にこれを強制します。

### 生成される型

ランタイムは`.react-server`ディレクトリに`react-server-routes.d.ts`ファイルを書き込みます。このファイルはルートごとのインターフェースで`@lazarv/react-server/routes`モジュールを宣言します：

```ts
// 自動生成 — 手動で編集しないでください
declare module "@lazarv/react-server/routes" {
  import type { RouteDescriptor, ValidateSchema } from "@lazarv/react-server/router";

  // ── /user/[id] ──
  interface UserRoute extends RouteDescriptor<"/user/[id]",
    typeof import("../pages/user/[id].page").validate extends
      { params: ValidateSchema<infer T> } ? T : { id: string },
    Record<string, string>
  > {
    createPage(
      component: (props: /* 推論されたパラメータ型 */) => React.ReactNode
    ): typeof component;
    createLoading(
      component: () => React.ReactNode
    ): typeof component;
  }
  export const user: UserRoute;
}
```

生成される型には、各ルートに存在するヘルパーのみが含まれます — ルートにページファイルがあれば`createPage`が、レイアウトがあれば正しいアウトレットプロップ付きの`createLayout`が得られます。このファイルは開発中とビルド時に自動的に再生成されるため、常にファイル構造と一致します。

> **注意:** ルートモジュールをバリデーション、レイアウト、アウトレット、ローディング状態、クライアント専用ルートと組み合わせた完全な動作アプリケーションについては、[typed-file-router](https://github.com/lazarv/react-server/tree/main/examples/typed-file-router)のサンプルをチェックしてください。

> プログラム的な型付きルーティングAPI（`createRoute`、`createRouter`、スキーマバリデーション戦略、パース関数など）については、[型付きルーター](/router/typed-router#create-route)ページを参照してください。

## 型付きルート

TypeScriptでファイルシステムベースのルーターを使用すると、ランタイムはファイル構造から型定義を自動的に生成します。これらの型は以下をカバーします：

- `Link`、`navigate`、`replace`、`prefetch`のすべての静的および動的ルートパス
- `[id]`や`[...slug]`などの動的セグメントから抽出されたルートパラメータ型
- `ReactServerComponent`で利用可能なアウトレット名

型チェックを有効にするには、生成された型をTypeScript設定に追加します：

```json filename="tsconfig.json"
{
  "compilerOptions": {
    // ...
  },
  "include": [/* ... */, ".react-server/**/*.ts"]
}
```

それだけです。ランタイムはファイル構造が変更されるたびに開発中に自動的に型を再生成し、ビルド時にも再生成します。IDEはすべてのルートパスのオートコンプリートを即座に表示し、無効なルートをコンパイル時にフラグします。

例えば、次のファイル構造がある場合：

```
app/
├── page.tsx          # /
├── about.tsx         # /about
├── user/
│   ├── [id].tsx      # /user/:id
│   └── page.tsx      # /user
└── posts/
    └── [...slug].tsx # /posts/*
```

`Link`コンポーネントと`navigate`関数は、`/`、`/about`、`/user`、`/posts`を静的ルートとしてオートコンプリートし、`/user/[id]`と`/posts/[...slug]`の型付きパラメータを提供します。

プログラム的な型付きルーティングAPI（`createRoute`、`createRouter`、型付きフック、スキーマバリデーションなど）については、[型付きルーター](/router/typed-router#create-route)を参照してください。