Next.js の App Router で 親layout を継承しない方法
Leaner Technologies の @corocn です。
最近は Next.js with App Router に入門しており、layout に関する学びを共有します。
本記事は Next.js v13.4.4 で検証しました。
子のpageで親のlayoutを継承したくない場合にどうするか?
まず App Router には Nested Layouts という機能があり、上層レイアウトの children
で定義した部分に下層のレイアウトまたはページが描画され、入れ子のように描画されます。
下層のレイアウト定義がシンプルになる一方、上層のレイアウトを無理やり修正することはできませんが、無視したいケースも一定存在します。そこで利用するのが Route Groups です。
Route Groups を利用する方法
Route Groups は "(auth)" のような括弧付きの記法です。Route Groups は URLのパスに影響を与えることなく、ディレクトリ・ファイルを論理的に分割することができます。
Route Groups を上手く利用することで、URLの構造を保ったままディレクトリを整理することができ、layout の適用範囲をコントロールすることができます。
Route Groups の例
例えば、次のようなルーティングを考えます。
path | 内容 |
---|---|
/ | ログイン後のページ |
/login | ログインページ |
これを愚直に定義すると次のようなディレクトリ構成になります。
/app
layout.tsx (root layout with サイドバー付き)
page.tsx (ログイン後のページ)
/login
page.tsx(サイドバーが表示されてしまう!)
サイドバーを描画したルートレイアウトを継承しているため、ログインページにもサイドバーが表示されてしまいます。
ここで Route Groups を利用すると、次のように整理することができます。
/app
layout.tsx (root layout)
(auth)
/login
layout.tsx(ログインページ用のレイアウト)
page.tsx
(authenticated)
layout.tsx (サイドバーを描画したログイン後のレイアウト)
page.tsx
layout はあくまで親ディレクトリを参照するため、ログインページにサイドバーが描画されることがなくなりました。
(auth) と (authenticated) は 実際のURLに反映されることはありません。あくまでディレクトリを整理するためだけに利用されます。
(ボツ) usePathname で分岐をしてみる
ログイン、非ログインのようにrootの近い場合の出し分けは Route Groups を利用すればよいのですが、ネストが深すぎてどうしようもないときもありそうです。
usePathname
を使って無理やりレンダリングを制御する方法を試してみました。
例えば、次のようなケースを考えてみます。
/app
layout.tsx (root layout)
(website)
layout.tsx
page.tsx(website用レイアウト)
/about
/company
page.tsx
/form
page.tsx(ここだけwebsite用レイアウトを使いたくない)
form 配下の page.tsx の描画で website用のレイアウトを継承したくないとします。
"use client"
import { usePathname } from "next/navigation";
export default function Condition(props) {
const pathname = usePathname();
const isPlainLayout = pathname === "/about/company/form";
const renderDefault = () => (
<div>
Default Layout
{ props.children }
</div>
)
const renderPlain = () => (
<div>
Plain Layout
{ props.children }
</div>
)
return isPlainLayout ? renderPlain() : renderDefault()
}
import Condition from "@/app/(website)/condition";
export default function WebsiteLayout({children}) {
return (
<div>
<Condition> { children } </Condition>
</div>
)
}
これで一応分岐できますが、ちょっと無理矢理感が否めません。
usePathname は Client Component only な hook のため、Client Component の中で Server Component を利用することになります。
今回は children に渡す形で利用しているので描画自体は成功していますが、最適化など、どこか落とし穴があるかもしれません。
Route Groups を使って分岐をしてみる
前述の例を Route Groups を使って分岐してみました。
/app
layout.tsx (root layout)
(website)
layout.tsx
page.tsx(website用レイアウト)
/about
/company
page.tsx
(plain)
layout.tsx
/about
/company
/form
/page.tsx
ディレクトリ構成が煩雑になるかな?と思いましたが、上層で分割されているため、意外と見やすいですね。いずれの場合も Route Groups を使うのが良さそうです。
まとめ
- レイアウトを継承したくない場合は Route Groups を利用してディレクトリ構成を見直すこと
- Route Groups は初見で「何この気持ち悪い記法」と思ったが、非常に便利
参考
- Routing: Route Groups | Next.js
- Functions: usePathname | Next.js
- Can we skip parent layout in a nested page in app dir? · vercel/next.js · Discussion #47686
採用
してます!
Discussion