Theming a Next.js App Without Flashing or Hydration Issues
Learn how to theme a Next.js app without visual flashing or hydration issues by setting initial theme state early and using CSS variables.
Theming a Next.js app without flashing or hydration issues comes down to one question: does the first server-rendered HTML already know which theme to show?
If the answer is no, users may see the wrong theme briefly before JavaScript loads. In the App Router, this can also create mismatches between server-rendered markup and client state.
Why theme flashes happen
A theme flash usually happens when the browser receives HTML styled with a default theme, then client-side JavaScript changes the theme after hydration.
Common causes include:
- Reading local storage only on the client.
- Applying theme classes in
useEffect. - Loading global theme CSS too late.
- Rendering different theme attributes on server and client.
- Depending on client state for initial colors.
The result is a page that visibly changes after load.
Set theme attributes early
The safest pattern is to apply theme attributes to the html or body element before the page hydrates.
In Next.js, the server layout can often read cookies or other request data and render the initial theme attribute immediately. That means CSS variables and theme selectors are already active when the browser paints.
Then a client provider can take over after hydration for interactive theme switching.
Use CSS variables for runtime themes
CSS variables are well suited to themeable apps because they can change at runtime without rebuilding CSS.
A theme can define variables like:
--background-color--text-color--primary-color--border-color--focus-outline-color
Components consume those variables, and the active theme controls their values.
This avoids hardcoding colors throughout the component tree.
Avoid server and client disagreement
Hydration issues happen when the server renders one version of the UI and the client expects another.
For theming, avoid patterns where the server renders an unknown default and the client immediately replaces it. Instead, make the initial theme deterministic.
Useful strategies include:
- Store the selected theme in a cookie.
- Resolve the theme in the root layout.
- Apply theme attributes during server render.
- Pass the initial theme into the client provider.
- Keep CSS variable names stable across themes.
This keeps the first paint and hydrated app aligned.
Support system preference carefully
System preference can be useful, but it should not create unpredictable markup.
If users have not chosen a theme, the server can choose a default or use a cookie set from a previous visit. Client-side system preference can still be respected, but make sure the rendered output does not jump after load.
Component libraries and theme setup
A themeable component library should document how to wire theme state in Next.js. This is not just an app concern. Components depend on theme variables, so the library should explain where those variables come from.
Boreal UI's approach is to combine global styles, server-readable theme attributes, and a client provider for switching. This keeps accessible component styling available early while still allowing interactive theme controls.
A practical checklist
Before shipping a themed Next.js app, check:
- Does the first HTML response include the correct theme attribute?
- Are global styles loaded once?
- Do components use CSS variables instead of hardcoded values?
- Is the client provider initialized with the same theme the server rendered?
- Does switching themes avoid layout shift?
- Are focus, border, and error colors readable in every theme?
Good theming should feel invisible. Users should not see the app figure itself out after load. The correct theme should already be there.