ファイルルートの規約

@react-router/fs-routesパッケージは、ファイル規約に基づいたルート設定を可能にします。

設定方法

まず、@react-router/fs-routesパッケージをインストールします。

npm i @react-router/fs-routes

次に、app/routes.tsファイルでルート設定を提供するために使用します。

app/routes.ts
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
 
export default flatRoutes() satisfies RouteConfig;

これはデフォルトでapp/routesディレクトリ内のルートを探しますが、アプリディレクトリを基準としたrootDirectoryオプションで設定できます。

app/routes.ts
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
 
export default flatRoutes({
  rootDirectory: "file-routes",
}) satisfies RouteConfig;

このガイドの残りの部分では、デフォルトのapp/routesディレクトリを使用していることを前提としています。

基本的なルート

app/routesディレクトリ内のモジュールは、アプリケーションのルートになります。ファイル名はルートのURLパス名に対応しますが、_index.tsxルートルートインデックスルートです。.js.jsx.ts.tsxのファイル拡張子を使用できます。

app/
├── routes/
│   ├── _index.tsx
│   └── about.tsx
└── root.tsx
URLマッチするルート
/app/routes/_index.tsx
/aboutapp/routes/about.tsx

これらのルートは、ネストされたルーティングのため、app/root.tsxのアウトレットでレンダリングされます。

ドット区切り記号

ルートファイル名に.を追加すると、URLに/が作成されます。

 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts.trending.tsx
│   ├── concerts.salt-lake-city.tsx
│   └── concerts.san-diego.tsx
└── root.tsx
URLマッチするルート
/app/routes/_index.tsx
/aboutapp/routes/about.tsx
/concerts/trendingapp/routes/concerts.trending.tsx
/concerts/salt-lake-cityapp/routes/concerts.salt-lake-city.tsx
/concerts/san-diegoapp/routes/concerts.san-diego.tsx

ドット区切り記号はネストも作成します。詳細はネストセクションを参照してください。

動的セグメント

通常、URLは静的ではなく、データ駆動型です。動的セグメントを使用すると、URLのセグメントに一致させ、その値をコードで使用できます。$プレフィックスを使用して作成します。

 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts.$city.tsx
│   └── concerts.trending.tsx
└── root.tsx
URLマッチするルート
/app/routes/_index.tsx
/aboutapp/routes/about.tsx
/concerts/trendingapp/routes/concerts.trending.tsx
/concerts/salt-lake-cityapp/routes/concerts.$city.tsx
/concerts/san-diegoapp/routes/concerts.$city.tsx

値はURLから解析され、さまざまなAPIに渡されます。これらの値を「URLパラメータ」と呼びます。URLパラメータにアクセスする最も便利な場所は、ローダーアクションです。

export async function serverLoader({ params }) {
  return fakeDb.getAllConcertsForCity(params.city);
}

paramsオブジェクトのプロパティ名は、ファイル名に直接マップされていることに注意してください。$city.tsxparams.cityになります。

ルートには、concerts.$city.$dateのように複数の動的セグメントを含めることができ、どちらも名前でparamsオブジェクトからアクセスできます。

export async function serverLoader({ params }) {
  return fake.db.getConcerts({
    date: params.date,
    city: params.city,
  });
}

詳細はルーティングガイドを参照してください。

ネストされたルート

ネストされたルーティングとは、URLのセグメントとコンポーネント階層およびデータの結合という一般的な概念です。詳細はルーティングガイドを参照してください。

ドット区切り記号を使用してネストされたルートを作成します。.の前にあるファイル名が別のルートファイル名と一致する場合、自動的に一致する親ルートの子ルートになります。これらのルートを考えてみましょう。

 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts._index.tsx
│   ├── concerts.$city.tsx
│   ├── concerts.trending.tsx
│   └── concerts.tsx
└── root.tsx

app/routes/concerts.で始まるすべてのルートは、app/routes/concerts.tsxの子ルートになり、親ルートのアウトレット内でレンダリングされます。

URLマッチするルートレイアウト
/app/routes/_index.tsxapp/root.tsx
/aboutapp/routes/about.tsxapp/root.tsx
/concertsapp/routes/concerts._index.tsxapp/routes/concerts.tsx
/concerts/trendingapp/routes/concerts.trending.tsxapp/routes/concerts.tsx
/concerts/salt-lake-cityapp/routes/concerts.$city.tsxapp/routes/concerts.tsx

ユーザーが親URLを直接訪問したときに親のアウトレット内で何かがレンダリングされるように、ネストされたルートを追加する場合は通常、インデックスルートを追加することをお勧めします。

たとえば、URLが/concerts/salt-lake-cityの場合、UI階層は次のようになります。

<Root>
  <Concerts>
    <City />
  </Concerts>
</Root>

レイアウトネストなしのネストされたURL

URLをネストしたいが、自動的なレイアウトネストは不要な場合があります。親セグメントに末尾の下線を追加することで、ネストをオプトアウトできます。

 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts.$city.tsx
│   ├── concerts.trending.tsx
│   ├── concerts.tsx
│   └── concerts_.mine.tsx
└── root.tsx
URLマッチするルートレイアウト
/app/routes/_index.tsxapp/root.tsx
/aboutapp/routes/about.tsxapp/root.tsx
/concerts/mineapp/routes/concerts_.mine.tsxapp/root.tsx
/concerts/trendingapp/routes/concerts.trending.tsxapp/routes/concerts.tsx
/concerts/salt-lake-cityapp/routes/concerts.$city.tsxapp/routes/concerts.tsx

/concerts/mineはもはやapp/routes/concerts.tsxとネストしませんが、app/root.tsxとネストします。trailing_アンダースコアはパスセグメントを作成しますが、レイアウトネストは作成しません。

trailing_アンダースコアを、親の署名の末尾にある長いビットと考えてください。これは、あなたを遺言から書き出し、続くセグメントをレイアウトネストから削除します。

ネストされたURLなしのネストされたレイアウト

これらをパスレスルートと呼びます。

URLにパスセグメントを追加せずに、ルートのグループとレイアウトを共有したい場合があります。一般的な例としては、パブリックページやログイン済みアプリエクスペリエンスとは異なるヘッダー/フッターを持つ認証ルートのセットがあります。これは_leadingアンダースコアを使用して行うことができます。

 app/
├── routes/
│   ├── _auth.login.tsx
│   ├── _auth.register.tsx
│   ├── _auth.tsx
│   ├── _index.tsx
│   ├── concerts.$city.tsx
│   └── concerts.tsx
└── root.tsx
URLマッチするルートレイアウト
/app/routes/_index.tsxapp/root.tsx
/loginapp/routes/_auth.login.tsxapp/routes/_auth.tsx
/registerapp/routes/_auth.register.tsxapp/routes/_auth.tsx
/concertsapp/routes/concerts.tsxapp/routes/concerts.tsx
/concerts/salt-lake-cityapp/routes/concerts.$city.tsxapp/routes/concerts.tsx

_leadingアンダースコアは、ファイル名の上に引っかける毛布のように考えてください。ファイル名をURLから隠します。

オプションのセグメント

ルートセグメントを括弧で囲むと、そのセグメントはオプションになります。

 app/
├── routes/
│   ├── ($lang)._index.tsx
│   ├── ($lang).$productId.tsx
│   └── ($lang).categories.tsx
└── root.tsx
URLマッチするルート
/app/routes/($lang)._index.tsx
/categoriesapp/routes/($lang).categories.tsx
/en/categoriesapp/routes/($lang).categories.tsx
/fr/categoriesapp/routes/($lang).categories.tsx
/american-flag-speedoapp/routes/($lang)._index.tsx
/en/american-flag-speedoapp/routes/($lang).$productId.tsx
/fr/american-flag-speedoapp/routes/($lang).$productId.tsx

/american-flag-speedo($lang).$productId.tsxではなく($lang)._index.tsxルートに一致する理由を疑問に思うかもしれません。これは、オプションの動的パラメーターセグメントの後に別の動的パラメーターがある場合、/american-flag-speedoのような単一セグメントのURLが/:lang /:productIdに一致するかどうかを確実に判断できないためです。オプションのセグメントは熱心に一致するため、/:langに一致します。このような設定がある場合は、($lang)._index.tsxローダーでparams.langを確認し、params.langが有効な言語コードでない場合は現在の/デフォルトの言語の/:lang/american-flag-speedoにリダイレクトすることをお勧めします。

スプラットルート

動的セグメントは単一のパスセグメント(URLの2つの/の間の部分)に一致しますが、スプラットルートはスラッシュを含むURLの残りの部分に一致します。

 app/
├── routes/
│   ├── _index.tsx
│   ├── $.tsx
│   ├── about.tsx
│   └── files.$.tsx
└── root.tsx
URLマッチするルート
/app/routes/_index.tsx
/aboutapp/routes/about.tsx
/beef/and/cheeseapp/routes/$.tsx
/filesapp/routes/files.$.tsx
/files/talks/react-conf_old.pdfapp/routes/files.$.tsx
/files/talks/react-conf_final.pdfapp/routes/files.$.tsx
/files/talks/react-conf-FINAL-MAY_2024.pdfapp/routes/files.$.tsx

動的ルートパラメーターと同様に、"*"キーを使用して、スプラットルートのparamsで一致するパスの値にアクセスできます。

app/routes/files.$.tsx
export async function serverLoader({ params }) {
  const filePath = params["*"];
  return fake.getFileInfo(filePath);
}

特殊文字のエスケープ

これらのルート規約で使用される特殊文字のいずれかをURLの一部にしたい場合は、[]文字を使用して規約をエスケープできます。これは、URLに拡張子が含まれるリソースルートに特に役立ちます。

ファイル名URL
app/routes/sitemap[.]xml.tsx/sitemap.xml
app/routes/[sitemap.xml].tsx/sitemap.xml
app/routes/weird-url.[_index].tsx/weird-url/_index
app/routes/dolla-bills-[$].tsx/dolla-bills-$
app/routes/[[so-weird]].tsx/[so-weird]
`app/routes/reports.$id[.pdf].ts`/reports/123.pdf

整理のためのフォルダ

ルートは、ルートモジュールを定義するroute.tsxファイルを含むフォルダにもできます。フォルダ内の他のファイルはルートになりません。これにより、他のフォルダで機能名を繰り返す代わりに、コードをそれらを使用するルートにより近づけて整理できます。

フォルダ内のファイルはルートパスには意味がなく、ルートパスはフォルダ名によって完全に定義されます。

これらのルートを考えてみましょう。

 app/
├── routes/
│   ├── _landing._index.tsx
│   ├── _landing.about.tsx
│   ├── _landing.tsx
│   ├── app._index.tsx
│   ├── app.projects.tsx
│   ├── app.tsx
│   └── app_.projects.$id.roadmap.tsx
└── root.tsx

それらのいくつか、またはすべてを、独自のrouteモジュールを含むフォルダにすることができます。

app/
├── routes/
│   ├── _landing._index/
│   │   ├── route.tsx
│   │   └── scroll-experience.tsx
│   ├── _landing.about/
│   │   ├── employee-profile-card.tsx
│   │   ├── get-employee-data.server.ts
│   │   ├── route.tsx
│   │   └── team-photo.jpg
│   ├── _landing/
│   │   ├── footer.tsx
│   │   ├── header.tsx
│   │   └── route.tsx
│   ├── app._index/
│   │   ├── route.tsx
│   │   └── stats.tsx
│   ├── app.projects/
│   │   ├── get-projects.server.ts
│   │   ├── project-buttons.tsx
│   │   ├── project-card.tsx
│   │   └── route.tsx
│   ├── app/
│   │   ├── footer.tsx
│   │   ├── primary-nav.tsx
│   │   └── route.tsx
│   ├── app_.projects.$id.roadmap/
│   │   ├── chart.tsx
│   │   ├── route.tsx
│   │   └── update-timeline.server.ts
│   └── contact-us.tsx
└── root.tsx

ルートモジュールをフォルダに変換すると、ルートモジュールはfolder/route.tsxになり、フォルダ内の他のすべてのモジュールはルートになりません。たとえば、

# これらは同じルートです。
app/routes/app.tsx
app/routes/app/route.tsx

# これらも同様です
app/routes/app._index.tsx
app/routes/app._index/route.tsx