Open4

RSC/App Routerで発生するpropsのバケツリレー

kage1020kage1020

Next.jsではdynamic routingのパラメータやSearchParamsを受け取る方法がmiddleware/サーバーコンポーネント(RSC)/クライアントコンポーネント(RCC)で異なる

middleware
export default function middleware(req: NextRequest) {
  const {pathname, searchParams } = req.nextUrl
}

https://nextjs.org/docs/app/api-reference/functions/next-request#nexturl

RSC(ページのみ)
// /foo/[locale]?q=hoge へのアクセス
export default Page({ params: { locale }, searchParams: { q } }: Props) {
  ...
}

https://nextjs.org/docs/app/api-reference/file-conventions/page#searchparams-optional

RCC
// /components/breadcrumbs
export default Breadcrumbs() {
  const params = useParams()
  const searchParams = useSearchParams()
}

https://nextjs.org/docs/app/api-reference/functions/use-params
https://nextjs.org/docs/app/api-reference/functions/use-search-params

kage1020kage1020

ページでないRSCでは現状route paramsやSearchParamsを直接取得する方法がなく,バケツリレーをするしか有効な手段がない

props-drilling
export default function ParentPage({ params: { locale } }: Props) {
  return <Child1 locale={locale} />
}

export function Child1({ locale }: Props) {
  // This component does not use locale
  return <Child2 locale={locale} />
}

export function Child2({ locale }: Props) {
  return <span>{translation[locale].foo}</span>
}
kage1020kage1020

検討案としてはReactのcacheを用いることでスコープ外から直接取り出す

https://github.com/vercel/next.js/discussions/45543?sort=top

import 'server-only';
import { cache } from 'react';

const requestContext = cache(() => {
  return new Map<string, string>();
});

export const setRequestContext = (key: string, value: string) =>
  requestContext().set(key, value);

export const getRequestContext = (key: string) => requestContext().get(key);

server-only-contextなんていうパッケージもある.比較的簡単に実装できる
https://github.com/manvalls/server-only-context

ただこの場合RSCをRCCの中で呼ばないという固い制約を結ばなければいけなくなり再利用性がなくなる可能性も

export function RSC() {
  const params = getRequestContext('params')
  return <SomeComponent />
}

'use client'
export function RCC() {
  return (
    <div>
      <RSC /> {/* これはエラー */}
    </div>
  )
}
kage1020kage1020

Reactのcacheは今のところexperimentalなのでNext.jsでは代わりにheadersを使えば破壊的変更には耐えられるかも
デフォルトでheaderからurlを取得する方法はないのでmiddlewareでセットする

export default function middleware(req: NextRequest) {
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('X-Pathname', request.nextUrl.pathname)
  requestHeaders.set('X-SearchParams', request.nextUrl.searchParams.toString())

  return NextResponse.next({
    request: {
      headers: requestHeaders
    }
  })
}
import 'server-only'
import { headers } from 'next/headers'

export function getParams() {
  const pathname = headers().get('X-Pathname')
  return pathname ? pathnameToParams(pathname) : {}
}

export function getSearchParams() {
  const searchParams = headers().get('x-SearchParams')
  return searchParams ? new URLSearchParams(searchParams) : new URLSearchParams()
}

https://zenn.dev/hayato94087/articles/418cf70ba5120b#middlewareの作成