📖

ドキュメントを読んだのでRemixのRouting(v2)を整理する

2023/07/05に公開
2

Remixのドキュメントに従って実例を交えながらRoute File Naming (v2)の解説を行います。

前提として、remix.config.jsでv2_routeConvention: trueを設定することを忘れないでください。

remix.config.js
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  future: {
    v2_routeConvention: true,
  },
};

ルートレイアウト(Root Route)

app/
├── routes/
└── root.tsx ← こいつのこと

Remixは、app/root.tsxにアプリケーション全体の共通処理や共通レイアウトを定義することができます。
例えば、ヘッダーやフッターといった共通レイアウトや、メタ情報を設定するためのHTMLヘッダーなどを記述することができます。
ただし、一部のページのみ対象から除外することが出来ない点に注意して実装してください。

以下の例はメタ情報とヘッダーフッターを設定する例です。
<Outlet />は子コンポーネントになります。childrenみたいなものだと考えてもらえれば問題ないと思います。

import { Outlet } from "@remix-run/react";

export default function App() {
  return (
    <html lang="ja">
      <head>
        {/* メタ情報などの設定 */}
      </head>
      <body>
        {/* 共通のヘッダーコンポーネント */}
        <Header />

        {/* ページ固有のコンテンツ */}
        <Outlet />

        {/* 共通のフッターコンポーネント */}
        <Footer />
      </body>
    </html>
  );
}

基本のルーティング(Basic Routes)

Remixでは、app/routes配下にページを配置したファイルがそのままアプリケーションのURLになります。
例えばapp/routes/home.tsxにファイルを配置すると、/homeのURLパスでhome.tsxのコンポーネントにアクセスできるようになります。

ディレクトリ構造の例
app/                               URLパス
├── routes/
│   ├── _index.tsx                 /
│   ├── home.tsx                   /home
└── root.tsx

ドット分割(Dot Delimiters)

Remix Routing(v2)では、ファイル名に.をつけることでURLの/を生成します。
例えばroutes/home.contents.tsxは、アプリケーション上ではURLパス/home/contentsでアクセスすることができます。

Remix Routing(v2)環境においては、routes/内にディレクトリを置くことはありません。
routes/setting/notification.tsxのような構成はしないことを注意してください。

ディレクトリ構造の例
app/                               URLパス
├── routes/
│   ├── _index.tsx                 /
│   ├── home.tsx                   /home
│   ├── home.contents.tsx          /home/contents
└── root.tsx

動的セグメント(Dynamic Segments)

Remixで動的セグメントを利用する場合$を利用します。
routes/user.$id.tsxは、URLパス/user/11111や、/user/12121でアクセスすることができます。

ディレクトリ構造の例
app/                               URLパス
├── routes/
│   ├── _index.tsx                 /
│   ├── home.tsx                   /home
│   ├── home.contents.tsx          /home/contents
│   ├── user.$id.tsx               /user/{任意の値}
└── root.tsx

動的セグメントで設定されたURLパラメータは、ローダーやアクションで取得することができます。次の例は、動的セグメントで指定されたidを使用して、対応するユーザー情報を取得するためのローダーの例です。

export function loader({ params }: LoaderArgs) {
  const userId = params.id
  return getUser(userId);
}

ネストを含むルーティング(Nested Routes)

Remixでは、ドットで分割されたファイル名の前の部分が一致するファイル同士は親子関係が形成されます。
例えば、home.contents.tsxhome.tsxの子要素になります。
このような場合、Remixでは子要素は親要素の<Outlet />内でレンダリングされます。そのため、子要素は親要素のレイアウトを引き継ぐことになります。

この時、親要素に<Outlet />の配置を忘れると子要素がレンダリングされないことに注意してください。

ディレクトリ構造の例
app/                               URLパス                   レイアウト
├── routes/
│   ├── _index.tsx                 /                        root.tsx
│   ├── home.tsx                   /home                    root.tsx
│   ├── home.contents.tsx          /home/contents           home.tsx
│   ├── user.$id.tsx               /user/{任意の値}          root.tsx
└── root.tsx

親コンポーネントの実装例

home.tsx
const Home = () => {
  /* homeの共通処理 */
  return (
    {/* homeの共通レイアウト */}
    <Outlet />
  );
};

export default Home;

この時、URLパス/homehome.tsxに直接アクセスするとhome<Outlet />部分がレンダリングされません。
しかし、インデックスルートファイル(hoge._index.tsx等)を配置することで、ユーザーが親要素に直接アクセスした際、<Outlet />が空のままページがレンダリングされることを防ぐことができます。
今回の場合は、home._index.tsxファイルを配置しています。
インデックスルートファイルも、親要素のレイアウトを引き継ぎ、親要素の<Outlet />内でレンダリングされます。

ディレクトリ構造の例
app/                               URLパス                   レイアウト
├── routes/
│   ├── _index.tsx                 /                        root.tsx
│   ├── home.tsx                                            root.tsx
│   ├── home._index.tsx            /home                    home.tsx
│   ├── home.contents.tsx          /home/contents           home.tsx
│   ├── user.$id.tsx               /user/{任意の値}          root.tsx
└── root.tsx

レイアウトが反映されない入れ子(Nested URLs without Layout Nesting)

子要素ファイル名の親セグメントの末尾に_をつけることで、URLを入れ子にしつつ、親要素のレイアウトは引き継がないことができます。

ディレクトリ構造の例
app/                               URLパス                   レイアウト
├── routes/
│   ├── _index.tsx                 /                        root.tsx
│   ├── home.tsx                                            root.tsx
│   ├── home._index.tsx            /home                    home.tsx
│   ├── home.contents.tsx          /home/contents           home.tsx
│   ├── home_.mine.tsx             /home/mine               root.tsx
│   ├── user.$id.tsx               /user/{任意の値}          root.tsx
└── root.tsx

追加したhome_.mine.tsxは、URL上ではhomeの配下に入りますが、レイアウトはroot.tsxが反映されています。

パスレスルート(Nested Layouts without Nested URLs)

子要素親セグメントの先頭に_をつけることで、URLは独立させたままレイアウトを共通にすることができます。

ディレクトリ構造の例
app/                               URLパス                   レイアウト
├── routes/
│   ├── _index.tsx                 /                        root.tsx
│   ├── home.tsx                                            root.tsx
│   ├── home._index.tsx            /home                    home.tsx
│   ├── home.contents.tsx          /home/contents           home.tsx
│   ├── home_.mine.tsx             /home/mine               root.tsx
│   ├── _home.auth.tsx             /auth                    home.tsx
│   ├── user.$id.tsx               /user/{任意の値}          root.tsx
└── root.tsx

追加された_home.auth.tsxは、home.tsxのレイアウトを引き継いでいるが、URLパスにhomeが含まれいないことがわかると思います。
親セグメントの先頭に_をつけることで、URLを隠すことができる。といった考え方が分かりやすいかもしれません。

まとめ

Remixのルーティングは

  1. ネストルーティング
  2. 動的セグメント
  3. レイアウトを含まないネスト
  4. パスレスルート

以上の4つを理解することで、基本的なルーティングは実現することができると思います。

この場では紹介しきっていないテクニックも存在しているため、今回紹介したテクニックのみで実現できない実装があれば、公式のドキュメントを眺めてみてください。

Discussion

nus3nus3

Remix v2のRouting、わかりやすくて参考になりました!

1点確認なのですが、ディレクトリ構造の例で紹介されている

app/                               URLパス                   レイアウト
├── routes/
│   ├── _index.tsx                 /                        root.tsx
│   ├── home.tsx                   /home                    root.tsx
│   ├── home.contents.tsx          /home/contents           home.tsx
│   ├── user.$id.tsx               /home/{任意の値}          root.tsx
└── root.tsx

user.$id.tsx /home/{任意の値} root.tsx
の部分は
user.$id.tsx /user/{任意の値} root.tsx
ではないでしょか?

弊社弊社

user.$id.tsx /user/{任意の値} root.tsxが正しいです!ご指摘ありがとうございます!