🫡

【Next.js検証】グローバルLayoutでparamsやsearchParamsは取れるのか?

に公開

はじめに

Headerなどの共通UIでURL情報を使いたいというケースはあると思いますが、Client Componentでしたら以下のような方法ですぐに取得できると思いますが今回はServer ComponentのLayoutではどうなのかを検証していきます。

// Client Componentでの取得方法
"use client"

import { usePathname, useSearchParams, useParams } from "next/navigation";

export default function Header() {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const params = useParams<{id?: string}>();
	
  return (
    <header>
      <div>pathname: {pathname}</div>
      <div>id: {params.id}</div>
      <div>tab: {searchParams.get("tab")}</div>
    </header>
  )
}

動的Layoutでの検証

app/[id]/layout.tsxのような動的セグメントを持つLayoutならparamsは取得可能になります。
ただし、searchParamsはここでは取得できません。
これは、LayoutがsearchParamsの変更によって再レンダリングされるのを防ぐためのNext.jsの仕様です。
searchParamsは、その階層のPageコンポーネント(例: app/[id]/page.tsx)でのみpropsとして受け取ることができます。


// app/[id]/layout.tsx
export default function Layout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { id: string };
}) {
  return (
    <>
      <div>ID: {params.id}</div>
      {children}
    </>
  );
}

グローバルLayoutでの検証

app/layout.tsxではparamssearchParamsは取得できません。

type RootLayoutProps = {
  children: React.ReactNode;
  params?: { [key: string]: string }; 👈 定義していますが取得できません
  searchParams?: { [key: string]: string | string[] | undefined }; 👈 定義していますが取得できません
};

export default function RootLayout({
  children,
  params,
  searchParams,
}: RootLayoutProps) {
  // ただし、Next.jsから渡されないので値は常にundefinedになる
  console.log("params:", params); // undefined
  console.log("searchParams:", searchParams); // undefined

  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  );
}

Layout層でparamsやpathを取得したい場合は素直にClientComponent化して取得するのが良いと思います。
ただし、どうしてもそのままServerComponentで取得したければ以下の方法でも取得することは可能になります。

グローバルLayoutでpathを取得する方法

middleware.tsでリクエストURLをカスタムヘッダーに詰めることでグローバルlayout内でheaders()から取得することができます。

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(req: NextRequest) {
  const requestHeaders = new Headers(req.headers)
  requestHeaders.set('x-current-path', req.nextUrl.pathname)
  requestHeaders.set('x-current-query', req.nextUrl.search)

  return NextResponse.next({
    request: { headers: requestHeaders },
  })
}

まずは上記のようにmiddlewareでsetしておきます。
そうすることで以下のようにServerComponent内で参照することが出来ます。

// app/layout.tsx(Server)
import { headers } from 'next/headers'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const header = headers()
  const path = header.get('x-current-path')   // 例: /articles/123
  const query = header.get('x-current-query') // 例: ?tab=info

  return (
    <html lang="ja">
      <body>
        {children}
      </body>
    </html>
  )
}

まとめ

動的Layoutではparamsは取得できますが、グローバルLayoutではparamsやsearchParamsは取得できません。
どうしてもServer側で必要ならmiddlewareを使う方法で取得可能になります。
ただし、グローバルLayoutはページ遷移時に再レンダリングされないため、middlewareで取得した値は最初のリクエスト時のまま更新されない点には注意が必要です。
常に最新のURLを扱いたい場合は、Client ComponentでusePathnameやuseSearchParamsを使うのがいいでしょう。

Discussion