Best Practices for mixing dynamic layout.tsx with static page.tsx?
Unanswered
Sloth bear posted this in #help-forum
Sloth bearOP
# 1. Setup
- Next.js (App Router) with React Compiler
-
- Header + footer live in
- Example: Country selector in the footer, rendered on the server with a default country, then updated on the client after reading a cookie
# 2. Current issue
This client-side update of the component happens before hydration finishes, producing this error:
My stop-gap is a
Downsides: A small pop-in and no markup for crawlers without JS.
# 3. Questions / doubts
Should
The word “force” sounds like a last-resort flag, yet the build fails if header-reading code stays static.
## Effect on child pages:
If
## Performance:
Does every first render now go through SSR, increasing latency and server load?
## ISR:
Could ISR cache common variants (e.g., no explicit country) so only a subset of requests hit SSR?
## Client navigation:
After the initial load, would routing to a fully static blog post still serve the prerendered result, or does the dynamic parent layout prevent that?
I would appreciate insights or real-world patterns for handling header-dependent layouts while keeping as much SSG speed as possible.
- Next.js (App Router) with React Compiler
-
layout.tsx
and almost all page.tsx
files are currently rendered statically- Header + footer live in
layout.tsx
; both need request headers/auth info- Example: Country selector in the footer, rendered on the server with a default country, then updated on the client after reading a cookie
# 2. Current issue
This client-side update of the component happens before hydration finishes, producing this error:
Hydration failed because the server rendered HTML didn't match the client. The tree will be regenerated on the client.
My stop-gap is a
useIsMounted
hook that waits for hydration before showing the component:import { useEffect, useState } from 'react';
export function useIsMounted(): boolean {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => { setIsMounted(true); }, []);
return isMounted;
}
Downsides: A small pop-in and no markup for crawlers without JS.
# 3. Questions / doubts
Should
layout.tsx
be marked dynamic?export const dynamic = 'force-dynamic';
The word “force” sounds like a last-resort flag, yet the build fails if header-reading code stays static.
## Effect on child pages:
If
layout.tsx
is dynamic, what happens to pages that could be static?## Performance:
Does every first render now go through SSR, increasing latency and server load?
## ISR:
Could ISR cache common variants (e.g., no explicit country) so only a subset of requests hit SSR?
## Client navigation:
After the initial load, would routing to a fully static blog post still serve the prerendered result, or does the dynamic parent layout prevent that?
I would appreciate insights or real-world patterns for handling header-dependent layouts while keeping as much SSG speed as possible.
15 Replies
Chum salmon
Didn't read your full question but per q2, you can use this to prevent hydration error:
This should make server and client match perfectly on first paint
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
setHydrated(true);
}, []);
This should make server and client match perfectly on first paint
I see you're writing something but I'll be back later ~
Sloth bearOP
That's what I'm currently doing, but this causes its own issues.
I apologize for my initial post being this long, but I would highly appreciate it if you could take a look at it 🙂
TLDR: I want to use SSR in my layout.tsx (instead of SSG with default values, that then change on the client) but still keep SSG for every page that can make use of it (think blog post).
Will making layout.tsx dynamic make the whole site dynamic?
I apologize for my initial post being this long, but I would highly appreciate it if you could take a look at it 🙂
TLDR: I want to use SSR in my layout.tsx (instead of SSG with default values, that then change on the client) but still keep SSG for every page that can make use of it (think blog post).
Will making layout.tsx dynamic make the whole site dynamic?
Chum salmon
If parent is dynamic, children will be dynamic, not the other way around. So your best option is wrapping that small dynamic part (country selector) in a client component.
Chum salmon
And I also don't quite understand what you mean by this:
"TLDR: I want to use SSR in my layout.tsx (instead of SSG with default values, that then change on the client) but still keep SSG for every page that can make use of it (think blog post)."
You do not need the layout to be ssr (just keep it ssg) if your goal is to prevent hydration issue from country selector. As mentioned, make country select its own client component and fix mismatch hydration there. Unless you have other things you need layout to be ssr?
"TLDR: I want to use SSR in my layout.tsx (instead of SSG with default values, that then change on the client) but still keep SSG for every page that can make use of it (think blog post)."
You do not need the layout to be ssr (just keep it ssg) if your goal is to prevent hydration issue from country selector. As mentioned, make country select its own client component and fix mismatch hydration there. Unless you have other things you need layout to be ssr?
Sloth bearOP
Ah, I should have made myself clearer. Also, I'm fairly new to Next.js, coming from Angular SPA development, so please bear with me 🙂
I want to avoid pop-in caused by components whose rendering is delayed until after hydration finishes. Preventing the hydration error is not enough and doesn't prevent the real issue at hand.
Because the header and footer components where this happens (they include auth-related links, country and currency selectors, etc.) are part of
But I don’t want pages that can benefit from SSG, like blog posts, to be rendered using SSR.
I am okay with the initial site load using SSR, but when a user navigates to a blog post, I want that view to load from a static build artifact and the content of
From what I see,
I want to avoid pop-in caused by components whose rendering is delayed until after hydration finishes. Preventing the hydration error is not enough and doesn't prevent the real issue at hand.
Because the header and footer components where this happens (they include auth-related links, country and currency selectors, etc.) are part of
layout.tsx
, it feels like I have to make layout.tsx
dynamic.But I don’t want pages that can benefit from SSG, like blog posts, to be rendered using SSR.
I am okay with the initial site load using SSR, but when a user navigates to a blog post, I want that view to load from a static build artifact and the content of
layout.tsx
to be replaced accordingly.From what I see,
layout.tsx
is not reloaded during page navigation. Instead, each page.tsx
seems to load as a partial result and integrates into the already existing site. So my assumption would be that these partial results could very well be SSG artifacts.Sloth bearOP
After writing the initial post I started doing some testing:
-
- Selector components are now server components that are returned in the correct state after the headers are read on the server
- I am currently serving a production build of the site locally using
- The initial navigation to the site returns a prerendered version of the landing page/blog post/any page (I assume using SSR)
- When I navigate from a page (let's say my landing page) to a blog post then a react fragment is returned
The raw data returned can be seen on the attached image. I am guessing this is not produced by SSR but is a SSG artifact?
-
layout.tsx
is now dynamic- Selector components are now server components that are returned in the correct state after the headers are read on the server
- I am currently serving a production build of the site locally using
next start
- The initial navigation to the site returns a prerendered version of the landing page/blog post/any page (I assume using SSR)
- When I navigate from a page (let's say my landing page) to a blog post then a react fragment is returned
The raw data returned can be seen on the attached image. I am guessing this is not produced by SSR but is a SSG artifact?
Chum salmon
So the concern is the UX?
First, I don't think you can have them finish render at the same time (SSR + CSR or SSG + CSR). SSR and SSG display HTML for the page almost instantly while CSR has to wait.
What do you think about using placeholder or skeletons so the layout won't jump?
PS: I do blog post to (with mdx). Initially it was super slow because it renders per request. What I did was using generateStaticParams() to prebuild all slug pages.
First, I don't think you can have them finish render at the same time (SSR + CSR or SSG + CSR). SSR and SSG display HTML for the page almost instantly while CSR has to wait.
What do you think about using placeholder or skeletons so the layout won't jump?
PS: I do blog post to (with mdx). Initially it was super slow because it renders per request. What I did was using generateStaticParams() to prebuild all slug pages.
Sloth bearOP
Yes, my blog post-related pages (overview, individual posts) also make use of
Placeholders could lessen the effect of pop-in, that's true, but a pre-rendered site can be handled better by crawlers.
SEO is a very important factor here (should have mentioned this earlier).
generateStaticParams
to generate static versions.Placeholders could lessen the effect of pop-in, that's true, but a pre-rendered site can be handled better by crawlers.
SEO is a very important factor here (should have mentioned this earlier).
@Sloth bear After writing the initial post I started doing some testing:
- `layout.tsx` is now dynamic
- Selector components are now server components that are returned in the correct state after the headers are read on the server
- I am currently serving a production build of the site locally using `next start`
- The initial navigation to the site returns a prerendered version of the landing page/blog post/any page (I assume using SSR)
- When I navigate from a page (let's say my landing page) to a blog post then a react fragment is returned
The raw data returned can be seen on the attached image. I am guessing this is not produced by SSR but is a SSG artifact?
Chum salmon
The page is SSG if:
- no
- no marked dynamic
- no
header()
or cookies()
or uncached fetch- no marked dynamic
Sloth bearOP
Regardless of
layout.tsx
being marked as dynamic? In that case everything seems to be what I need it to be.When I add a console log statement to my static blog post page, then it is logged when navigating to it using
next start
. This points towards SSR.Chum salmon
I have to go to bed now I hope you can achieve what you aim for.
Sloth bearOP
Sure thing, thanks for your input 🙂
Chum salmon