Remixの次のメジャーバージョンにて導入される新しいルートファイルの命名規則について

# はじめに

Remixの次期メジャーバージョンでは、新しいルートファイルの命名規則が導入される予定です。
実はこの機能、`remix.config.js`の`future`ブロックにて、`v2_routeConvention`を`true`に設定することで一足先に試すことができるようになっています。

```remix.config.js
module.exports = {
  future: {
    v2_routeConvention: true,
  },
};
```

:::note warn
この機能は、Remixのバージョン1.11.0から利用可能となっているため、それ以前のバージョンをご使用の場合はご注意ください。
:::

# V1からV2に移行すべき人

V2の主な目的は、**フラットルートファイルシステム規約**のサポートです。

簡単にいうと、今までパスを区切るためにフォルダをネストさせていました。
このため、特定のファイルを開くために何層ものフォルダを潜らないといけません。

```md
app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts/
│   │   ├── trending.tsx
│   │   ├── salt-lake-city.tsx
│   │   └── san-diego.tsx
└── root.tsx
```

フラットルートファイルシステム規約がサポートされると、パスを表現するためにフォルダをネストさせる必要がなくなり、1つの階層にすべてのファイルを置くことができるようになるため、生産性が大きく向上されことが期待されています。

```md
app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts.trending.tsx
│   ├── concerts.salt-lake-city.tsx
│   └── concerts.san-diego.tsx
└── root.tsx
```

ただ、今まで通りフォルダをネストすることでURLのパスを表現する方が好きだという人は無理に移行する必要はありません。従来通り、何も気にせず開発を続けることが公式に推奨されています。

# ドット区切り

ファイル名を`.`で区切ることで、URL上でパスを区切ることができます。

```md
app/
├── routes/
│ ├── \_index.tsx
│ ├── about.tsx
│ ├── concerts.trending.tsx
│ ├── concerts.salt-lake-city.tsx
│ └── concerts.san-diego.tsx
└── root.tsx
```

例えば、次のファイル名は、次のURLに一致します。

| URL                      | Matched Route               |
| ------------------------ | --------------------------- |
| /concerts/trending       | concerts.trending.tsx       |
| /concerts/salt-lake-city | concerts.salt-lake-city.tsx |
| /concerts/san-diego      | concerts.san-diego.tsx      |

# ネストされたルート

ドット区切りのファイルは、ネストされたルートを表すことができます。この時の親子関係は次のようになります。

例えば、次のようなファイル構成になっているとします。

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

それぞれのルートを`<Outlet />`を通してレンダリングする親に相当するレイアウトファイルとの関係は次のようになります。
つまり、`concerts.xxx.tsx`のように`concerts`で始まっていて、`routes`フォルダの中に`concerts.tsx`が存在するのであれば、この`concerts.tsx`がレイアウトファイルとして利用されます。

| URL                      | Matched Route         | Layout       |
| ------------------------ | --------------------- | ------------ |
| /                        | \_index.tsx           | root.tsx     |
| /about                   | about.tsx             | root.tsx     |
| /concerts                | concerts.\_index.tsx  | concerts.tsx |
| /concerts/trending       | concerts.trending.tsx | concerts.tsx |
| /concerts/salt-lake-city | concerts.$city.tsx    | concerts.tsx |

しかし、URL上は変更したくないけれども、レイアウトファイルとして`concerts.tsx`ではなく、`root.tsx`に親になってもらいたい場合があります。
その場合は、`concerts`に`_`を付与して、`concerts\_.xxx.tsx`というファイル名を使用します。

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

この場合はの対応は次のようになります。

| URL                      | Matched Route         | Layout       |
| ------------------------ | --------------------- | ------------ |
| /                        | \_index.tsx           | root.tsx     |
| /concerts/mine           | concerts\_.mine.tsx   | root.tsx     |
| /concerts/trending       | concerts.trending.tsx | concerts.tsx |
| /concerts/salt-lake-city | concerts.$city.tsx    | concerts.tsx |

URLのパスには親レイアウトファイルの名前は入れたくないけども、グルーピングとして共通の親レイアウトファイルを持ち場合があります。これを**パスレスルート**と呼んでいますが、これを実現するためには、そのセグメント名の先頭に`\_`を付与します。

```md
app/
├── routes/
│ ├── \_auth.login.tsx
│ ├── \_auth.register.tsx
│ ├── \_auth.tsx
│ ├── \_index.tsx
│ ├── concerts.$city.tsx
│ └── concerts.tsx
└── root.tsx
```

この場合のURLとファイルの対応は次のようになります。

| URL                      | Matched Route       | Layout       |
| ------------------------ | ------------------- | ------------ |
| /                        | \_index.tsx         | root.tsx     |
| /login                   | \_auth.login.tsx    | \_auth.tsx   |
| /register                | \_auth.register.tsx | \_auth.tsx   |
| /concerts/salt-lake-city | concerts.$city.tsx  | concerts.tsx |

これにより、URLから`auth`の文字を隠すことができるようになっています。

# ダイナミックセグメント

動的にURLの特定のセグメントをマッチさせることで、その値を`params`から受け取るようにすることができます。特定のセグメントを動的に決定させるためには、`$`を使用します。

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

例えば、次のファイル名は、次のURLに一致します。
この場合、`$city`に`salt-lake-city`が入るため、`loader()`と`action()`にて`params.city`を使うことでその値にアクセスすることができます。

| URL                      | Matched Route         |
| ------------------------ | --------------------- |
| /concerts/trending       | concerts.trending.tsx |
| /concerts/salt-lake-city | concerts.$city.tsx    |
| /concerts/san-diego      | concerts.$city.tsx    |

ちなみに、`concerts.$city.$date.tsx`というファイル名を設定することも可能となっています。
この場合、`params.city`と`params.date`にてそれぞれのセグメントの値にアクセスすることができます。

# オプショナルセグメント

`\$`が付いたルートセグメントをカッコで囲むことで、そのセグメントは任意の扱いとなります。

```md
app/
├── routes/
│ ├── ($lang)._index.tsx
│   ├── ($lang).$productId.tsx
│   └── ($lang).categories.tsx
└── root.tsx
```

これにより、`/categories`でも`/en/categories`でもアクセスできるようになります。
主な対応は次のとおりです。

| URL                      | Matched Route          |
| ------------------------ | ---------------------- |
| /                        | ($lang).\_index.tsx    |
| /categories              | ($lang).categories.tsx |
| /en/categories           | ($lang).categories.tsx |
| /fr/categories           | ($lang).categories.tsx |
| /american-flag-speedo    | ($lang).$productId.tsx |
| /en/american-flag-speedo | ($lang).$productId.tsx |
| /fr/american-flag-speedo | ($lang).$productId.tsx |

# スプラットセグメント

スプラットセグメントを行うために、ファイル名の拡張子の前に必ず`$`を使います。
これにより、URLの残りの部分とマッチさせることができるようになります。

```md
app/
├── routes/
│ ├── \_index.tsx
│ ├── $.tsx
│   ├── about.tsx
│   └── files.$.tsx
└── root.tsx
```

例えば、`files.$.tsx`は、`/files/a/b/c/d/e/f/g`にもマッチします。

| URL                                        | Matched Route |
| ------------------------------------------ | ------------- |
| /                                          | \_index.tsx   |
| /beef/and/cheese                           | $.tsx         |
| /files/                                    | files.$.tsx   |
| /files/talks/remix-conf_old.pdf            | files.$.tsx   |
| /files/talks/remix-conf_final.pdf          | files.$.tsx   |
| /files/talks/remix-conf-FINAL-MAY_2022.pdf | files.$.tsx   |

`params`で値にアクセスするためには、`params["*"]`を使います。

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

# 特殊文字のエスケープ

URLの一部として、`$`や`[]`などの特殊文字を使用したい場合は、`[`と`]`で値を囲むことでエスケープすることができるようになっています。

| Filename                      | URL                |
| ----------------------------- | ------------------ |
| routes/sitemap[.]xml.tsx      | /sitemap.xml       |
| routes/[sitemap.xml].tsx      | /sitemap.xml       |
| routes/weird-url.[_index].tsx | /weird-url.\_index |
| routes/dolla-bills-[$].tsx    | /dolla-bills-$     |
| routes/[[so-weird]].tsx       | /[so-weird]        |

#　フォルダを使ったルート定義

今までは、ファイル名でURL上のセグメントを定義して、ファイルの中身としてルートモジュールを定義していました。
実はフォルダ名として、ルートパスを定義し、そのフォルダ内の`route.tsx`でルートモジュールを定義するという方法があります。

例えば、今までは次のようにドット区切りを使ってルートパスを定義していました。そしてそれぞれのファイルがルートモジュール本体を表していました。

```md
routes/
  _landing._index.tsx
  _landing.about.tsx
  _landing.tsx
  app._index.tsx
  app.projects.tsx
  app.tsx
  app_.projects.$id.roadmap.tsx
```

このルートパスの定義をフォルダ側に移して、その中に`route.tsx`というファイルを作成します。そして、その`route.tsx`内でルートモジュールを定義します。
このとき、そのフォルダ内にある`route.tsx`以外のファイルは、ルートパスとしての役割を一切果たしません。そのため、そのフォルダ内の`route.tsx`でしか使用されない、そのパスでの機能に特化した関数やコンポーネントのファイルを配置することが可能となり、整理することができるようになっています。

その一部、あるいは全部が、内部に独自のルートモジュールを保持するフォルダーになることもあります。

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

例えば、次のフォルダとファイルの関係は同じものを意味しています。

```md
# these are the same route:
routes/app.tsx
routes/app/route.tsx

# as are these
routes/app._index.tsx
routes/app._index/route.tsx
```
この方法は、Remixで最も推奨されている方法であり、そのルートでしか使用されないモジュールとすべてのルートで共通のモジュールを簡単に認識できるようになるため、ファイル構造が散らかるのを防ぎ、リファクタリングの容易性を格段に向上させることが期待されています。

# まとめ

基本的な考え方は、V1がベースとなっているため、V1の命名規則に慣れており、ある程度不満を抱えていた開発者にとっては待望の機能となっています。
また、V1のままでもファイル整理疲れを起こさずに管理できるという開発者にとっては、あまりメリットが感じられないと思うので、無理に移行させることはお勧めしません。

# 参考にした記事

https://remix.run/docs/en/main/file-conventions/route-files-v2

https://github.com/remix-run/remix/discussions/4482