App Router でのパンくずやナビメニューの作成に useSelectedLayoutSegments が便利
Leaner Techonologies の @corocn です。
最近 Nuxt.js のアプリを Next.js + App Router に載せ替えたのですが、メニューの描画に useSelectedLayoutSegments が便利だったので軽く紹介します。
実は App Router のドキュメントをちゃんと読んでいくと、Functionsの最後で紹介されています。
何ができる?
メニューを描画するときに、現在のページのメニューだけ下線や太字で装飾したいケースがありますよね。このとき普通に実装しようとするとURLからベースとなるパスを除いた上で分割して...など若干面倒です。
useSelectedLayoutSegment(s) を使うと、レイアウトを起点として、URLをいい感じにパースして返してくれるのでスッキリ書くことができます。
単数形(segment)と複数形(segments)の2種類の hook が用意されており、単数形の場合はレイアウトの直下1階層のみを string で取得し、複数形の場合はネストされたURLのセグメントを string の配列で取得できます。
useSelectedLayoutSegment
useSelectedLayoutSegments
※画像は 2023-09-27 時点の例を、公式より引用。
実装例
"use client"
import Link from "next/link"
import { useSelectedLayoutSegment } from "next/navigation"
export default function Menu() {
const segment = useSelectedLayoutSegment()
const items = [
{ path: 'posts', name: '投稿'},
{ path: 'friends', name: '友達' },
{ path: 'mypage', name: 'マイページ '}
]
return (
<ul>
{
items.map((item) => {
const isActive = item.path === segment
return (
<li key={item.path} className={isActive ? 'font-bold' : ''}>
<Link href={`/sample/blog/${item.path}`}>{item.name}</Link>
</li>
)
}
)}
</ul>
)
}
雑ですがこんな感じでしょうか。
細かい話
動的ID や Route Groups も取得される
動的IDや、Route Groups(URLに現れない括弧付きのディレクトリ)も取得されます。
不要な場合は適宜 filter() することが推奨されています。
// ディレクトリ /mypage/(secure)/friends/[friendId]/edit/page.tsx
// URL /mypage/friends/1/edit
=> ['mypage', '(secure)', 'friends', '35', 'compare']
Server Component に直接書けない
useSelectedLayoutSegment(s) は Client Component 専用の hook ですが、メニューを定義したいであろう layout.js はデフォルト Server Component のため直接は利用できません。メニューを Client Component で作成してインポートして使う必要があります。
要するにレイアウトにべた書きせず、ちゃんとコンポーネントで分けてねってことになります。よっぽど分けると思いますが。
内部実装
正規表現か何かでパスをパースしてるのかな? と思いましたが、LayoutRouterContext から tree を取得して、再帰的に見ているようでした。たしかに Route Groups や Parallel Routes にも対応しているから パスを見るだけじゃダメそうですね。
tree には leaf node に __PAGE__
という文字列が入っているので、これが再帰終了条件になります。
勉強になりましたが、RouterContext周りの挙動はもうちょっと調べてみたなと思いました。
Discussion