🙂

App Router でのパンくずやナビメニューの作成に useSelectedLayoutSegments が便利

2023/10/16に公開

Leaner Techonologies の @corocn です。

最近 Nuxt.js のアプリを Next.js + App Router に載せ替えたのですが、メニューの描画に useSelectedLayoutSegments が便利だったので軽く紹介します。

実は App Router のドキュメントをちゃんと読んでいくと、Functionsの最後で紹介されています。

https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment

https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments

何ができる?

メニューを描画するときに、現在のページのメニューだけ下線や太字で装飾したいケースがありますよね。このとき普通に実装しようとすると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 で作成してインポートして使う必要があります。

要するにレイアウトにべた書きせず、ちゃんとコンポーネントで分けてねってことになります。よっぽど分けると思いますが。

内部実装

https://github.com/vercel/next.js/blob/f3cb952873600c3fe14c85fd21086efe3a9d8175/packages/next/src/client/components/navigation.ts#L222C1-L228C2

正規表現か何かでパスをパースしてるのかな? と思いましたが、LayoutRouterContext から tree を取得して、再帰的に見ているようでした。たしかに Route Groups や Parallel Routes にも対応しているから パスを見るだけじゃダメそうですね。

https://github.com/vercel/next.js/blob/f3cb952873600c3fe14c85fd21086efe3a9d8175/packages/next/src/client/components/navigation.ts#L205C1-L206C79

tree には leaf node に __PAGE__ という文字列が入っているので、これが再帰終了条件になります。

勉強になりましたが、RouterContext周りの挙動はもうちょっと調べてみたなと思いました。

リーナーテックブログ

Discussion