はじめに
Remixの次期メジャーバージョンでは、新しいルートファイルの命名規則が導入される予定です。
実はこの機能、remix.config.js
のfuture
ブロックにて、v2_routeConvention
をtrue
に設定することで一足先に試すことができるようになっています。
module.exports = {
future: {
v2_routeConvention: true,
},
};
この機能は、Remixのバージョン1.11.0から利用可能となっているため、それ以前のバージョンをご使用の場合はご注意ください。
V1からV2に移行すべき人
V2の主な目的は、フラットルートファイルシステム規約のサポートです。
簡単にいうと、今までパスを区切るためにフォルダをネストさせていました。
このため、特定のファイルを開くために何層ものフォルダを潜らないといけません。
app/
├── routes/
│ ├── _index.tsx
│ ├── about.tsx
│ ├── concerts/
│ │ ├── trending.tsx
│ │ ├── salt-lake-city.tsx
│ │ └── san-diego.tsx
└── root.tsx
フラットルートファイルシステム規約がサポートされると、パスを表現するためにフォルダをネストさせる必要がなくなり、1つの階層にすべてのファイルを置くことができるようになるため、生産性が大きく向上されことが期待されています。
app/
├── routes/
│ ├── _index.tsx
│ ├── about.tsx
│ ├── concerts.trending.tsx
│ ├── concerts.salt-lake-city.tsx
│ └── concerts.san-diego.tsx
└── root.tsx
ただ、今まで通りフォルダをネストすることでURLのパスを表現する方が好きだという人は無理に移行する必要はありません。従来通り、何も気にせず開発を続けることが公式に推奨されています。
ドット区切り
ファイル名を.
で区切ることで、URL上でパスを区切ることができます。
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 |
ネストされたルート
ドット区切りのファイルは、ネストされたルートを表すことができます。この時の親子関係は次のようになります。
例えば、次のようなファイル構成になっているとします。
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
というファイル名を使用します。
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のパスには親レイアウトファイルの名前は入れたくないけども、グルーピングとして共通の親レイアウトファイルを持ち場合があります。これをパスレスルートと呼んでいますが、これを実現するためには、そのセグメント名の先頭に\_
を付与します。
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
にてそれぞれのセグメントの値にアクセスすることができます。
オプショナルセグメント
\$
が付いたルートセグメントをカッコで囲むことで、そのセグメントは任意の扱いとなります。
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の残りの部分とマッチさせることができるようになります。
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["*"]
を使います。
// 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
でルートモジュールを定義するという方法があります。
例えば、今までは次のようにドット区切りを使ってルートパスを定義していました。そしてそれぞれのファイルがルートモジュール本体を表していました。
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
でしか使用されない、そのパスでの機能に特化した関数やコンポーネントのファイルを配置することが可能となり、整理することができるようになっています。
その一部、あるいは全部が、内部に独自のルートモジュールを保持するフォルダーになることもあります。
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
例えば、次のフォルダとファイルの関係は同じものを意味しています。
# 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のままでもファイル整理疲れを起こさずに管理できるという開発者にとっては、あまりメリットが感じられないと思うので、無理に移行させることはお勧めしません。
参考にした記事