- Published on
Overcoming Next.js' Search Params Limitation in Layouts
Not many people know about this but parallel routes is awesome
- Authors
- Name
- Nico Prananta
- Follow me on Bluesky
In Next.js App Router, the Layout component doesn't have access to the search params because a shared layout is not re-rendered during navigation, which could lead to stale searchParams
between navigations. This limitation has caused many developers to believe that Next.js App Router is not a good choice for applications where layout is used to display information based on the value in the search params.
For example, you may have a user impersonation feature where the admins of your app can impersonate other users by changing the value of the user
search param. Then you want to display the user's name in the layout. However, the layout doesn't have access to the search params because it's a shared layout. Only page components get the search params.
The answer to this predicament is to use parallel routes. Using the parallel routes, a layout can render multiple pages at the same time. But that definition is hiding the true power of parallel routes. A page is basically a React component. So using the parallel routes, a layout can render multiple React components at the same level for a certain route, not only a single children.
So in the user impersonation example, we can have a layout that renders the main page as children
of the layout and another component that renders the impersonated user's name based on the user
search param.
// layout.tsx
export default function Layout({
children,
impersonation,
}: {
children: React.ReactNode
impersonation: React.ReactNode
}) {
return (
<div className="flex h-screen flex-col space-y-2 p-4 font-sans text-black">
<div className="space-y-2 pb-8 pt-6 md:space-y-5">
<h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
This is a dashboard
</h1>
{impersonation} {/* Render impersonation here */}
</div>
<div className="flex flex-col">{children}</div> {/* Render children here */}
</div>
)
}
In the layout above, the Layout
component receives two props: children
and impersonation
. The children
prop is the React component that is rendered at the root level of the page. The impersonation
prop will be filled with the component defined in @impersonation/page.tsx
.
// @impersonation/page.tsx
import { Suspense } from 'react'
import Content from './content'
export default async function Page({ searchParams }: { searchParams: Record<string, string> }) {
const user = searchParams['user'] as string
return (
<Suspense key={user || 'you'} fallback={<div>Loading...</div>}>
<Content user={user} />
</Suspense>
)
}
// @impersonation/content.tsx
import { fakeUserData } from '../data'
export default async function Content({ user }: { user?: string }) {
if (user) {
await new Promise((resolve) => setTimeout(resolve, 1000)) // fake delay
const userData = fakeUserData[user as keyof typeof fakeUserData]
if (userData) {
return <div>Viewing this page as {userData.name}</div>
}
}
return <div>Viewing this page as yourself</div>
}
Since the @impersonation/page.tsx
component is a page component, it will receive the search params as props. And now the impersonated user's name is displayed in the layout! You can check the demo page to see the resul and the code in this repo.
By the way, I'm making a book about Pull Requests Best Practices. Check it out!