はじめに
NextjsのApp Routerはapp
配下のディレクトリ構造でルーティングを行い、そこに様々な状況に応じたUIをファイル名をもとに定義します。
ディレクトリ構造によるルーティングは簡単にいうと、app/
直下においたファイルは/
にアクセスしたときのUIとして、app/dashboard
直下に置いたファイルは/dashboard
にアクセスしたときのUIとして扱われます。
表示させたいメインのUIをpage
、読み込み中のUIをloading
、エラー時のUIをerror
、全体のレイアウトのUIをlayout
という名前のファイルに対して定義します。これらを用いて構築されるページは以下のようなReactコンポーネントと同等の内容が描画されます(他にも紹介しなかった状態のUIを定義するファイル名は存在しますし、UIに関連しないが特別に扱われるファイル名も存在します)。
<Layout>
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loading />}>
<Page />
<Suspense>
<ErrorBoundary>
</Layout>
Layout
はlayout
に定義したコンポーネント、Error
はerror
に定義したコンポーネント、Loading
はloading
に定義したコンポーネント、Page
はpage
に定義したコンポーネントです。
Page
コンポーネントより外の部分はよりディレクトリが深いところで引き継がれます。
<OuterLayout>
<ErrorBoundary fallback={<OuterError />}>
<Suspense fallback={<OuterLoading />}>
<Layout>
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loading />}>
<Page />
<Suspense>
<ErrorBoundary>
</Layout>
</Suspense>
</ErrorBoundary>
</Layout>
このようにディレクトリ構造とファイル名を活用するApp Routerですが、Parallel Routes
と言われる機能があります。これを用いることで同じパスに対する挙動を異なるディレクトリで宣言することができます。
Paralle Routes
App Routerはディレクトリ構成に従ったルーティングをしますが、ディレクトリ名の形式によって特別な挙動をすることがあります。Paralle Routes
はそのようなディレクトリ名を特別に扱うことで利用できる機能の1つです。
この機能はディレクトリ名を@
から始まる名前にすることで有効になります。そのディレクトリに宣言したUIは1つ浅いディレクトリに定義されたlayout
で扱えます
app/@hello
とapp/@world
にUIがそれぞれ宣言されていることを考えます。通常のルーティング通りであればそこ定義したUIは/@hello
や/@world
にアクセスしたときに扱われますが、ディレクトリ名が@
から始まっている場合はこのパスにアクセスできません。代わりにapp
直下にあるlayout
ファイルで以下のようにUIを呼び出せます。
export default function Layout(props: {
children: React.ReactNode
hello: React.ReactNode
world: React.ReactNode
}) {
return (
<html>
<body>
{props.children}
{props.hello}
{props.world}
</body>
</html>
)
}
app
直下のlayout
がprops
として通常のchildren
の他に/app/@hello
と/app/@world
に宣言されたUIをhello
やworld
から受け取リます。受け取るprops
の名前はディレクトリ名から@
をとったものです。
Paralle Routes
はapp/@hello/accounts
のように内部でディレクトリを積み重ねてルーティングを構築できます。パスが/accounts
の画面ではprops.hello
の部分にapp/@hello/accounts
で定義したUIが表示されます。
app/accounts
ディレクトリにファイルを置いて/accounts
にアクセスしてもapp/@hello/accounts
やapp/@world/accounts
にUIが宣言されていない場合はapp
直下のnot-found
ファイルに書かれたUIかデフォルトの404ページが表示されます。ただし、NextjsのLink
などのナビゲーション機能を用いて遷移した場合はその部分に直前まで表示していたUIを表示し続けます。
/accounts
に直接アクセスしたときにUIを表示させたい場合はapp/@hello/accounts
とapp/@world/accounts
にUIを宣言するか、app/@hello
とapp/@world
にdefault
ファイルを宣言させる必要があります。
default
ファイルはParalle Routes
内に定義されていないパスにアクセスしたときに返すデフォルトのUIを宣言するものです。すべてのルートを制御するのは難しいため、not-found
を返すようにしたいとき以外はdefault
ファイルにUIを宣言しておくのが良いと考えています。
使い方
ページ内で異なる意味のUIを表示させたい場合に読み込みやエラーの状態を分割するためにParalle Routes
を利用することが最も簡単なケースです。
export default function Layout({
accounts,
dashboard,
}: {
accounts: React.ReactNode
dashboard: React.ReactNode
}) {
retrurn (
<>
{accounts}
{dashboard}
</>
)
}
この例のようにすることで、アカウントに関するUIとダッシュボードに表示するUIを分離して表示させます。
Paralle Routes
はこの他にも条件によって異なるをUIを出し分ることが可能です。
export default function Layout({
children,
login,
}: {
children: React.ReactNode
login: React.ReactNode
}) {
const { isLoaded, userId } = useAuth();
if (!isLoaded || !userId) {
return login;
}
retrurn children
}
ログインしている時はユーザーに特化した内容を、ログインしていない場合はログインを促す画面やユーザーに依存していない画面を表示させます。
ログインユーザーに対するルーティングを行うディレクトリとログインしていないユーザーに対するルーティングを行うディレクトリで分割できるところに利点があります。
さらにIntercepting Routes
とParalle Router
を組み合わせて使う方法も有名です。
Intercepting Routes
についての詳細は紹介しませんが、この機能も特別なディレクトリ名を付けることによって有効になります。(.)
や(...)
や(..)(..)
のようなものが先頭についている場合に利用でき、ルーティングを先頭についた文字列をもとにハックします。
app/dashboard/(..)photos
というディレクトリがあるとします。/dashboard
から/photos
にNextjsのLink
などを用いた遷移を行った場合にapp/photos
に宣言されたUIの代わりにapp/dashboard/(..)photos
に宣言したUIを表示します。
こんなIntercepting Routes
はParalle Router
と組み合わせてある機能を作ることができます。
export default function PhotoModal({ photo }) {
return (
<Modal>
<Photo photo={photo} />
</Modal>
)
}
export default function Photo({ photo }) {
return <Photo photo={photo} />
}
export default function Layout({
children,
modal,
}: {
children: React.ReactNode
modal: React.ReactNode
}) {
return (
<>
{props.children}
{props.modal}
</>
)
}
export default function Photos() {
return (
<>
{photos.map((photo) => (
<Link key={photo.id} href="/photos/photo.id">
<Photo photo={photo} />
</Link>
)}
</>
)
}
app
直下と@modal
直下にはdefault
ファイルが置かれており、null
を返すようになっているとします。
このように定義された時のルーティングは以下のようになります。
/
にアクセスした時は、@modal
側は返すものがないのでnull
を返し、app/page.tsx
に宣言されたUIからリンク付きのPhoto
を複数表示させます。
Photo
にあるリンクを使って/
から/photo/:id
にアクセスした時はapp/@modal
に(..)photo/[id]
が存在するので、app/photo/[id]
のUIを表示せずに、app/@modal/(..)photo/[id]
に宣言したUIを表示させます。
/photo
に直接アクセスした時はapp/@modal
側はnull
を返し、app/photo/[id]
に宣言したUIが表示されます。
このように組み合わせることで以下のサイトのような機能を作成できます。
Intercepting Routes
とParalle Router
を使うことで一覧から詳細へ訪れる時は背景で一覧をちらつかせつつモーダルを表示させ、別の場所や直接のアクセスがあった時は詳細をページ全体に表示させるようなルーティングを簡単に実装できると言うわけです。
おわりに
Paralle Routes
を紹介しました。App Routerは直感的なルーティング機能が提供しています。そんな中で命名による特別なルーティングを行うと直感的な部分が損なわれてしますので使い過ぎには気をつけた方が良いです。
例えば、UIの意味によって分けるために利用すると、ルーティングの複雑さに対してメリットが小さいのでこの機能を利用せずにファイル内でSuspense
やErrorBoundary
などの境界を引いて分離した方が良いと考えています。
しかし、ログインの有無によって表示させるコンテンツを切り替えるようなケースでは第2のディレクトリをParalle Routes
を用いて記述することで綺麗に切り分けられるので有意義に利用できますし、最後に紹介したようなアクセスのされ方によって表現方法を変えるような実装はこれを使わなければより複雑な実装になってしまうと考えられるのでそのようなケースではParalle Routes
は有効的と考えています。
便利な機能ですが、本当にParalle Routes
が有効的か考えてから利用するように注意していきましょう!