SSR Material UI v5 with Emotion: Style Injection Architecture for Next.js
Architecture note for SSR Material UI v5+ with Emotion in Next.js. Prevent FOUC and hydration mismatches with a minimal theme and cache design.
19 Jul 2025, 02:14 UTC

The problem is FOUC and hydration mismatch, not just missing styles
With Material UI v5+ using Emotion by default, server-rendered pages flash unstyled content and throw hydration warnings when the Emotion cache is created differently on server and client, or when theme values diverge between the initial HTML and the first client render. The useful takeaway is to treat the initial HTML payload as the style contract: the server owns theme resolution and critical CSS extraction, the client must hydrate with an identical cache and theme, and any browser-only preference must be deferred.
Requirements for safe SSR
SSR with Emotion requires three invariants. First, a single source of truth for theme creation so server and client compute the same palette, spacing and typography for a given request. Second, an Emotion cache with a stable insertion point in the document head so extracted styles are injected in the correct order relative to MUI baseline styles. Third, framework glue that extracts critical styles on the server and reuses the server-inserted markup on the client.
An insertion point is a DOM marker, typically a script or meta tag with a known id, that Emotion uses as the anchor for style tags. Without it, Emotion appends to the end of head and specificity can invert, breaking MUI overrides.
Smallest suitable design
The design can be expressed as three shared pieces plus framework glue.
Theme factory
Keep theme creation pure and input-driven. Accept only request-scoped inputs such as a cookie value. Do not read window, matchMedia, localStorage or navigator during render.
// lib/createTheme.ts
export function createAppTheme(mode: 'light' | 'dark') {
return {
palette: { mode },
// other stable config
};
}
The factory must be synchronous and deterministic for a given mode.
Emotion cache factory
Create a new cache per request on the server. The cache is configured with a key and an insertion point id that matches the marker rendered in the document head.
// lib/createEmotionCache.ts
import createCache from '@emotion/cache';
export function createEmotionCache(key: string) {
return createCache({
key,
insertionPoint: 'insertion-point',
});
}
This cache is used by the server to collect critical styles.
Framework glue
In Next.js Pages Router, style extraction happens in the custom document via Emotion server helpers. In App Router, a client-side registry uses the server-inserted-HTML hook, and streaming SSR makes insertion order correctness mandatory rather than optional.
Trust and data boundaries
The trust boundary is the initial HTML payload: the server owns theme resolution and critical CSS, the client must hydrate with identical theme and cache, and any runtime-only state like user dark-mode preference must be deferred or cookie-resolved to avoid mismatch.
Caution: Do not read window, localStorage, or matchMedia during the first render; gate dark-mode toggles behind an effect or resolve them from cookies on the server.
Operational checks
Disable JavaScript and confirm styled HTML. Inspect server responses for style tags preceding body markup. Monitor console for hydration warnings. Track layout shift metrics after deploy.
Failure modes and conditions
Failure modes include FOUC when the insertion point is missing or incorrectly ordered, and hydration mismatch when theme mode is computed from browser globals during render. Specificity inversion occurs if custom Emotion styles are injected before MUI baseline styles.
The design should change if you adopt a CSS-variables theme mode, move to a zero-runtime CSS pipeline, drop SSR for a pure SPA, or migrate between Next.js router generations, because each changes where styles are generated and who owns the style contract.
Verification
To verify the design, build the app, fetch the HTML with JavaScript disabled, and confirm components render with correct fonts, spacing, and colors before any client script executes. Run the dev server and search the browser console for hydration mismatch warnings after toggling theme. Inspect the served HTML head to confirm the emotion insertion-point marker appears before extracted style chunks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.