Cannot get search params in server side layout file, need help figuring out how to structure!
Answered
Brown bear posted this in #help-forum
Brown bearOP
So I have a somewhat complex dashboard component. At the base route there is an elaborate screen showing lots of data and tons of filtering functions which are stored in searchParams for initial data load on the server component. This works fine as is but I realized I needed to nest a series of complex subcomponents underneath this dashboard.
So I moved my mounting of the dashboard to the layout, so that children, instead of being this page, could now be whatever subsequent folders I have nested within this dashboard route. That sort of worked, except my layout component cannot get search params, only page.tsx can get search params, but at the dashboard route level page.tsx is just a null component meant to fill the currently empty child space.
So basically i need help understanding how to structure this. I have a complex dashboard that is driven by searchParams. This needs to be able to load data on the servercomponent initial load. Then I need to support nested routing within this component to more detailed views of various things. However in order ot support children living on top of the dashboard, the only way I can figure out how to do that would be to mount hte dashboard within the layout, since pages cannot have childrne themselves.
What am I missing?
I know parallel routes could potentially work at the dashboard level, but as soon as I navigate one level deeper, it seems parallel routes stop working and it throws a 404
So I moved my mounting of the dashboard to the layout, so that children, instead of being this page, could now be whatever subsequent folders I have nested within this dashboard route. That sort of worked, except my layout component cannot get search params, only page.tsx can get search params, but at the dashboard route level page.tsx is just a null component meant to fill the currently empty child space.
So basically i need help understanding how to structure this. I have a complex dashboard that is driven by searchParams. This needs to be able to load data on the servercomponent initial load. Then I need to support nested routing within this component to more detailed views of various things. However in order ot support children living on top of the dashboard, the only way I can figure out how to do that would be to mount hte dashboard within the layout, since pages cannot have childrne themselves.
What am I missing?
I know parallel routes could potentially work at the dashboard level, but as soon as I navigate one level deeper, it seems parallel routes stop working and it throws a 404
Answered by Brown bear
in
middleware.ts
const response = NextResponse.next();
response.headers.set(
'searchParams',
request.nextUrl.searchParams?.toString() || '',
);
return response;
55 Replies
Layout files do not get searchParams through props.
I would suggest that you try and see if you can refactor it to have the page receive searchParams again, Layouts are supposed to be UI that’s shareable across your pages under that URL segment not to have your entire app mounted.
If you have a dynamic dashboard then you should consider using Client Components to handle all dashboards stuff.
What you could do is instead of having your layout render and do everything, make your page fetch the initial data, use searchParams coming from props. And then pass that data down to a client component that will handle the rest
I would suggest that you try and see if you can refactor it to have the page receive searchParams again, Layouts are supposed to be UI that’s shareable across your pages under that URL segment not to have your entire app mounted.
If you have a dynamic dashboard then you should consider using Client Components to handle all dashboards stuff.
What you could do is instead of having your layout render and do everything, make your page fetch the initial data, use searchParams coming from props. And then pass that data down to a client component that will handle the rest
—————— dashboard/page.tsx
—————— Dashboard.tsx
export default async function DashboardPage({searchParams}){
const sp = await searchParams;
// whatever you need to do server side
return <Dashboard initialData={} />
}
—————— Dashboard.tsx
“use client”
export default function Dashboard({initialData}){
const searchParams = useSearchParams();
//….
return (
<>
your whole dashboard, with nested component, all client …
</>
)
}
Brown bearOP
The problem is. now I cannot do any server fetching on the nested children. Sure I can nest components within the Dashboard client itself, but now I cannot use server components to fetch any data on the server for the nest children of the dashboard!
And also I would losing routing if I just nest the children within the dashboard client. It must have children so I can continue to use the app router to get the paths to the children instead of fixing it
So I could have a url like
/dashboard/user/1
. The dashboard server component will fetch data on the server side, and it needs access to searchParams to do so. And a url of /dashboard
would be valid. However if the user clicks into a user on the dashboard, it should nest a user componetn wihtin the dashboard, and that is routable, so it will become /dashboard/user/1
. Sure on the navigation from dashboard to user 1 we could do it client side, but if someone shares that url, I want all data for the initial fetch to happen within server componentsI apologize if i am explaining this poorly
Still somewhat new to nextjs
@Brown bear And also I would losing routing if I just nest the children within the dashboard client. It must have children so I can continue to use the app router to get the paths to the children instead of fixing it
You don't have to lose routing, you will still have that nesting if you want. This is the idea i had..
--------- localhost/dashboard/
--------- localhost/dashboard/user/1
--------- localhost/dashboard/
/app/dashboard/layout.tsx
=> server component - wrapping layout that all dashboard routes will share/app/dashboard/page.tsx
=> server component - can do Server Side data fetching, either to render UI or pass down to client components. If you keeppage.tsx
in the server you can also use Metadata APIs /app/dashboard/Dashboard.tsx
=> client component - Your actual Dashboard interactive component, imagine this is directly what page.tsx
will render, except it will be a client component. Dashboards are interactive anyway, you will require the Client sooner than later--------- localhost/dashboard/user/1
/app/dashboard/[user]/layout.tsx
=> server component - Nested layout, this will appear nested under /app/dashboard/layout.tsx
from the higher level./app/dashboard/[user]/page.tsx
=> server component - Fetch specific data for user 1 or 2 or 3 on the server, using {params, searchParams}
, dynamic stuff. The page will apear nester under [user]/layout.tsx
.Brown bearOP
but the rendering of
/app/dashboard/[user]/layout.tsx
will no longer render the dashboard. Thats the issue. The dashboard needs to co-exist with the user pageLike eralistically, the dashbaord is part of hte layout. The biggest issue here is that, for whatever reason, layout files do not have access to searchParams
I am tempted to just use a workaround I found in middleware to inject search params into headers and access teh headers in the layout component. This one limitations is, as it were, quite limiting
Oh wait I was seeing “dashboard” as the general concept.
Dashboard will always show no matter how nested you are in the routes?
Dashboard will always show no matter how nested you are in the routes?
Brown bearOP
Yes!
Basically when routing, the user compoennt will mount inside the dashboard
Mmmh wait I might have a crazy idea that’s not that hacky but let me try first lol
Brown bearOP
ok ok. Just for what its worth, this is working:
Brown bearOP
in
middleware.ts
const response = NextResponse.next();
response.headers.set(
'searchParams',
request.nextUrl.searchParams?.toString() || '',
);
return response;
Answer
Brown bearOP
and then in
layout.tsx
: const searchParams = (await headers()).get('searchParams');
const urlSearchParams = new URLSearchParams(searchParams || undefined);
const foo = urlSearchParams.get('foo');
Now I think there are two caveats above, I believe it won't re-render the layout when the params change, and it may also make every single layout dynamic now, but thati s fine since htat is the basis of my app more or less
The first problem is ok too because I am only ever doing the initial load on the server, if params change they should be updated client side anyway
@LuisLl Mmmh wait I might have a crazy idea that’s not *that hacky* but let me try first lol
Brown bearOP
BTW if your idea revolves around parrallel routes, I tried it for about an entire day and ended up bashing my head against the wall 😛
@Brown bear BTW if your idea revolves around parrallel routes, I tried it for about an entire day and ended up bashing my head against the wall 😛
A hacky way to do parallel components...
Parallel routes might work but I've never deeply tried them so I'm not the best to recommend them..
Brown bearOP
If it involves
[...catchAll]
its a poor solution because catch all re-renders on every single slug changeOr at least that was my discovery after finally gettng them sort of kind of working (thoguh incredibly buggy)
parrallel routes are miserable to work with
the solution I posted above seems to work
Though I would argue the nextjs team really needs to get on top of making searchParams available in a layout component. It is HUGELY limiting for dynamic apps
I really do appreciate your help though @LuisLl !
I'll mark this as solved (though more like hacked)
Brown bearOP
What does your folder structure look like?
/dashboard/page.tsx
right now its returning null, but if it returns something it gets placed along the dashboardBrown bearOP
And does this work for hard navigation or only soft
@Brown bear And does this work for hard navigation or only soft
Let me set up some links lol
Brown bearOP
If oyu get this working I would love a code sandbox to look around in. I still worry about how often parallel routes tend to re-render
It works, let me push this to my repo, it's a throwaway project where I dump code like this and patterns lol, Ill make a branch for you
Brown bearOP
You're an absolute king
You dropped this 👑
Have in mind that I won't look as clean as it can be, i had to make it work with what previosly was😂 also, you will have to find a good way to keep search params state between navigations, or idk what is your plan with searchParams, if they will be per page or they have to persist through navigations
I added a little <Timer> component that's simple a client component that takes an initial time passed from the server so you can see the Layout wont re-render, therefore cient components dont receive a new Date() on navigations
@Brown bear If oyu get this working I would love a code sandbox to look around in. I still worry about how often parallel routes tend to re-render
Let me know how it goes when you take a look
Brown bearOP
Wouldn't this only work for one level of depth? Looking at the the repo the magic is in [slug] but what if I needed n-levels of routing within the dashboard itself?
But hte main issue is
this doesn't solve the key problem, that being that I cannot load the initital data for the dashboard server side
or within a server component
We are merely using a client component as dashboard in order to get the searchParams
Brown bearOP
It is decently complex, that being said, injecting the headers with searchparams does effectively solve this for my entire use case
Though for someone else it could create a buggy nightmare due to the un-updating nature of search params in layout
This feels like a really obvious use of routing though so I am surprised nextjs doesn't view this as a pretty glaring issue