How to set cookies in layout.tsx
Answered
Scottish Fold posted this in #help-forum
Scottish FoldOP
I fetch profile for the logged in user in my layout.tsx file.
And render the components based on if the user is admin or not.
For fetching the profile I have to refresh the access token if it has expired and update the cookies with the new access token.
But I am getting an error
Error: Cookies can only be modified in a Server Action or Route Handler
And render the components based on if the user is admin or not.
For fetching the profile I have to refresh the access token if it has expired and update the cookies with the new access token.
But I am getting an error
Error: Cookies can only be modified in a Server Action or Route Handler
Answered by joulev
Nope, it won’t, don’t worry. Pages are still server components unless you explicitly "use client"
26 Replies
@Scottish Fold I fetch profile for the logged in user in my layout.tsx file.
And render the components based on if the user is admin or not.
For fetching the profile I have to refresh the access token if it has expired and update the cookies with the new access token.
But I am getting an error
Error: Cookies can only be modified in a Server Action or Route Handler
Not possible. Refresh cookies in middleware instead
Scottish FoldOP
Hey @joulev, should I call the fetch for profile in middleware.ts file?
And then how do you pass the fetched data back to layout file?
@Scottish Fold Hey <@484037068239142956>, should I call the fetch for profile in middleware.ts file?
Is fetching the profile absolutely critical to refresh tokens?
Scottish FoldOP
No, basically i have created a helper function that handles all the fetch all and refreshes the token if it is expired,
But since layout is a server component I don't have a request object to pass to that function, and hence I am reimplementing the refresh token logic in case of fetching profile data
But since layout is a server component I don't have a request object to pass to that function, and hence I am reimplementing the refresh token logic in case of fetching profile data
export async function makeRequest(
request: NextRequest,
url: string,
requestOptions: RequestInit,
method: string,
skipSession: boolean = false
)
request: NextRequest,
url: string,
requestOptions: RequestInit,
method: string,
skipSession: boolean = false
)
This is the function signature for that function, it add headers and refreshes token if required
It is impossible to set cookies in server components. This is a fundamental limitation, you can’t do anything about it.
Middleware runs in a separate scope compared to server components, so it’s not trivial to pass data between middleware and RSCs. It is possible but not what you should do here.
You should instead make separate logic for RSCs and the middleware. In the middleware only runs the minimum logic needed to rotate the token.
Middleware runs in a separate scope compared to server components, so it’s not trivial to pass data between middleware and RSCs. It is possible but not what you should do here.
You should instead make separate logic for RSCs and the middleware. In the middleware only runs the minimum logic needed to rotate the token.
Scottish FoldOP
Is possible to create a request object in the server component?
Then I would be able to call the make request function and it will handle the rest of the logic.
Then I would be able to call the make request function and it will handle the rest of the logic.
Even if you could recreate the request object in server components, you can’t send the Set-Cookie header required for setting cookies to work. You can’t change the response status and headers in server components, this is a fundamental flaw of the server component system design and there are no workarounds for it.
Scottish FoldOP
Oh ok, so the only solution is to write the rotate token logic in the middle ware and data fetching for RSC in my helper function?
Make separate functions, one to run in middleware that does nothing except rotating cookies, and one to run in server components that does everything else
Scottish FoldOP
But I only refresh the token when i receive 401 status from the backend.
And if the refresh request also gives 401, which means the refresh token is also expired.
Then I log the user out.
How would I make sure that we only try to rotate the tokens if we are getting 401.
And if the refresh request also gives 401, which means the refresh token is also expired.
Then I log the user out.
How would I make sure that we only try to rotate the tokens if we are getting 401.
Then you have to fetch the backend to see if it 401’s
@joulev Then you have to fetch the backend to see if it 401’s
Scottish FoldOP
Can you please give a sample code on how to do this?
I have found another approach that almost works.
Layout.tsx
return (
#Unknown Channel
<NavbarLayout />
<div className="flex overflow-hidden">
<div className="basis-60 shrink-0 bg-side_bg relative">
<Sidebar />
</div>
<div className="grow p-4 bg-main_bg overflow-auto">{children}</div>
</div>
</>
)
Layout.tsx
return (
#Unknown Channel
<NavbarLayout />
<div className="flex overflow-hidden">
<div className="basis-60 shrink-0 bg-side_bg relative">
<Sidebar />
</div>
<div className="grow p-4 bg-main_bg overflow-auto">{children}</div>
</div>
</>
)
Moved the profile data fetching logic to a client component NavbarLayout. This way it uses the make request function under the hood
NavbarLayout.tsx
if (isAdmin && !profileData?.user.isAdmin) {
console.log("running")
return <UnAuthorizedError error="Unauthorized access" />
}
if (profileData) {
return <Navbar profileData={profileData} />
}
Here is render the required component s conditionally
if (isAdmin && !profileData?.user.isAdmin) {
console.log("running")
return <UnAuthorizedError error="Unauthorized access" />
}
if (profileData) {
return <Navbar profileData={profileData} />
}
Here is render the required component s conditionally
But if I am rendering the UnAuthorizedError component I don't want the children and sidebar from layout to render.
Is there a way to stop that?
Is there a way to stop that?
Well… if you are doing client side rendering, that works too. In that case just make the layout a client component and conditionally render <Sidebar /> and children
Instead of client-side rendering inside NavbarLayout, just do the logic inside the layout directly
Scottish FoldOP
Won't it turn all the children complete of layout (which is my entire app) in a client component?
Nope, it won’t, don’t worry. Pages are still server components unless you explicitly "use client"
Answer
Scottish FoldOP
Oh yes, I tried and it is working like charm.
Thanks @joulev for the help.
You made a lot of my concepts clear today ☺️
You made a lot of my concepts clear today ☺️
You’re welcome