PATTERN Cited by 1 source
Mount-gated client-only rendering¶
Pattern. For content that should genuinely differ between
server-side and client-side rendering (device-specific banners,
viewport-dependent UI, anything that uses navigator.* or
window.*), render a fallback (or null) on both SSR and initial
CSR, then flip to the real content after the component mounts on
the client.
The flip is driven by a standard React lifecycle shape:
const [isMounted, setIsMounted] = React.useState(false);
React.useEffect(() => {
setIsMounted(true);
}, []);
return (
<div>
{isMounted ? dataThatDiffersBetweenClientAndServer : "some fallback" || null}
</div>
);
(Source: sources/2023-07-10-zalando-rendering-engine-tales-road-to-concurrent-react.)
On SSR, isMounted is false → fallback. On initial CSR
hydration, same value → fallback. Markup matches. No
mismatch. After hydration
completes, the useEffect runs, setIsMounted(true), component
re-renders with the real content.
Shape of mismatch this solves¶
Instead of (SSR renders one thing, CSR renders different thing → mismatch):
const { dataThatDiffersBetweenClientAndServer } = props;
return <div>{dataThatDiffersBetweenClientAndServer}</div>;
Do:
const [isMounted, setIsMounted] = React.useState(false);
React.useEffect(() => { setIsMounted(true); }, []);
return (
<div>{isMounted ? dataThatDiffersBetweenClientAndServer : "some fallback" || null}</div>
);
When this pattern is correct¶
- Device / browser / OS detection that relies on client-only
APIs (
navigator.userAgent,window.matchMedia, etc). - App-download banners tailored to the user's device.
- Personalisation / preferences only readable from
localStorageor cookies the server doesn't see. - Component variants whose choice depends on client feature detection (WebGL available? WebCrypto subtle?).
When NOT to reach for this pattern¶
- Timezone / locale mismatches — patterns/backend-localization-for-hydration-stability is a better architectural fix; mount-gating these just delays the render and breaks SEO (the server-rendered content doesn't carry the localised value).
- Timer / time-delta content — patterns/suppress-hydration-warning-for-unavoidable-mismatch preserves the initial SSR value without a post-mount flip.
- Anything that should be in the initial SEO-indexed content — mount-gating hides the real content from Googlebot's stateless render unless the fallback carries the same semantic content.
- Above-the-fold critical UI — the fallback → real-content flip is visible; if it happens above the fold the user sees a flash of the fallback followed by the real content.
Layout-shift risk¶
The post calls this out explicitly: "in such cases, be mindful of layout shifts that can happen as a result of some element popping into the view." (Source: sources/2023-07-10-zalando-rendering-engine-tales-road-to-concurrent-react.)
Mitigations:
- Size the fallback to match the real content's bounding
box — e.g. a
height/min-height-set placeholder. - Use skeleton UI that occupies the same region with a neutral-colour pattern.
- Position absolutely if the gate is decorating existing content rather than replacing it.
- Consider hiding the region with CSS
visibility: hiddenuntil mount-gated, if the content's bounding box is SSR-knowable.
Trade-offs vs alternatives¶
| Approach | SSR output | CSR flip | Preserves SEO | Hydration-safe |
|---|---|---|---|---|
| Mount-gated rendering | fallback | yes | only if fallback is semantic | ✅ |
suppressHydrationWarning |
server value | implicit | yes | ✅ (narrow) |
| Server-value-matching-client (via explicit args) | correct | no | yes | ✅ |
| Backend-computed value | correct | no | yes | ✅ |
Read: if you can make server and client agree (by passing explicit timezone / locale / whatever), that's always better than mount-gating — no flip, no layout shift, no SEO cost. Mount-gating is the escape hatch for genuinely client-only data.
Interaction with concurrent rendering¶
Under React 18 / concurrent rendering + streaming SSR, the mount-gated pattern still works as described but the timing of the flip is tied to when the boundary that contains it is hydrated, not when the whole page finishes hydrating. This means the flip can happen before the rest of the page is interactive — slightly earlier than React 17, but with the same semantics.
Seen in¶
- sources/2023-07-10-zalando-rendering-engine-tales-road-to-concurrent-react — canonical wiki instance. Documented as the general escape hatch for SSR-vs-CSR genuinely-different content in Zalando Rendering Engine, with the layout-shift caveat called out explicitly.
Related¶
- concepts/hydration-mismatch — the problem this sidesteps.
- concepts/react-hydration — the mechanism.
- patterns/suppress-hydration-warning-for-unavoidable-mismatch — alternative for semantically-equivalent mismatches.
- systems/react — the runtime.