Understanding & Fixing FOUC in Next.js App Router (2025 Guide)

FOUC (Flash of Unstyled Content) is a common but frustrating visual glitch that can happen in modern React apps — especially in Next.js App Router — where your layout appears unstyled briefly during page load or refresh. What is FOUC? FOUC happens when the browser renders HTML before your CSS or styles are applied — resulting in a flash of unstyled or partially styled elements. In SSR frameworks like Next.js, this often happens due to mismatches between the server-rendered HTML and client-rendered UI. Symptoms of FOUC On refresh, your UI shows raw HTML (unstyled) for a moment before styles kick in. CSS variables like --primary-color aren't applied until the page fully loads. -The warning: Warning: A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. Common Causes of FOUC in Next.js App Router Dynamic Theming (e.g., per-tenant styles via Redux/localStorage) When you load styles after hydration, like setting a theme color based on the current tenant: useEffect(() => { document.documentElement.style.setProperty('--primary-color', tenantColor); }, []); This runs only after the page loads, so the browser renders unstyled HTML first → FOUC. Using Date.now(), Math.random(), or typeof window in shared components These values differ between server and client → causes hydration mismatch, and sometimes FOUC. 3rd-Party CSS Libraries Not Loaded Early Enough If you're importing styles from UI libraries (e.g., PrimeReact, Tailwind CSS) inside components, they are loaded too late — causing unstyled content to flash on screen momentarily. // Inside a component file (not recommended) import 'primereact/resources/themes/lara-light-indigo/theme.css'; How to Fix FOUC in Next.js App Router 1.Always Import Global CSS in layout.tsx app/layout.tsx import "primeflex/primeflex.css"; import "primereact/resources/themes/lara-light-indigo/theme.css"; import "./globals.scss" This ensures styles are loaded before your app renders. 2.Provide Fallback Theme Styles in globals.scss :root { --primary-color: #ff8c00; // fallback before tenant-specific color is applied } This avoids flashing unstyled buttons or layouts before tenant styles load. 3.Wrap Client-Side Logic in a Hydration Guard Use this in a client component (e.g., HydrationWrapper.tsx): "use client"; import { useEffect, useState } from "react"; export default function HydrationWrapper({ children }) { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); if (!mounted) return null; return {children}; } Then use it in layout.tsx: {children} This delays rendering until the client takes control — preventing mismatches. Bonus: Avoid These Anti-Patterns ❌ Avoid dynamic CSS imports. ❌ Don’t use Date.now() or Math.random() directly in render. ❌ Don’t read from localStorage or window in server components. ❌ Don’t conditionally render or in client components. FOUC is annoying — but totally fixable in Next.js App Router. By carefully separating your server logic from client-specific theming or rendering, you can eliminate style flashes, avoid hydration warnings, and ensure a polished user experience.

Apr 9, 2025 - 14:48
 0
Understanding & Fixing FOUC in Next.js App Router (2025 Guide)

FOUC (Flash of Unstyled Content) is a common but frustrating visual glitch that can happen in modern React apps — especially in Next.js App Router — where your layout appears unstyled briefly during page load or refresh.

What is FOUC?

FOUC happens when the browser renders HTML before your CSS or styles are applied — resulting in a flash of unstyled or partially styled elements.

In SSR frameworks like Next.js, this often happens due to mismatches between the server-rendered HTML and client-rendered UI.

Symptoms of FOUC

  • On refresh, your UI shows raw HTML (unstyled) for a moment before styles kick in.

  • CSS variables like --primary-color aren't applied until the page fully loads.

-The warning: Warning: A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.

Common Causes of FOUC in Next.js App Router

  1. Dynamic Theming (e.g., per-tenant styles via Redux/localStorage) When you load styles after hydration, like setting a theme color based on the current tenant:
useEffect(() => {
  document.documentElement.style.setProperty('--primary-color', tenantColor);
}, []);

This runs only after the page loads, so the browser renders unstyled HTML first → FOUC.

  1. Using Date.now(), Math.random(), or typeof window in shared components
    These values differ between server and client → causes hydration mismatch, and sometimes FOUC.

  2. 3rd-Party CSS Libraries Not Loaded Early Enough
    If you're importing styles from UI libraries (e.g., PrimeReact, Tailwind CSS) inside components, they are loaded too late — causing unstyled content to flash on screen momentarily.

// Inside a component file (not recommended)
import 'primereact/resources/themes/lara-light-indigo/theme.css';

How to Fix FOUC in Next.js App Router

1.Always Import Global CSS in layout.tsx

 app/layout.tsx
import "primeflex/primeflex.css";
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "./globals.scss"

This ensures styles are loaded before your app renders.

2.Provide Fallback Theme Styles in globals.scss

:root {
  --primary-color: #ff8c00; // fallback before tenant-specific color is applied
}

This avoids flashing unstyled buttons or layouts before tenant styles load.

3.Wrap Client-Side Logic in a Hydration Guard
Use this in a client component (e.g., HydrationWrapper.tsx):

"use client";
import { useEffect, useState } from "react";

export default function HydrationWrapper({ children }) {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) return null;

  return <>{children};
}

Then use it in layout.tsx:


  
    {children}
  


This delays rendering until the client takes control — preventing mismatches.

Bonus: Avoid These Anti-Patterns
❌ Avoid dynamic CSS imports.
❌ Don’t use Date.now() or Math.random() directly in render.
❌ Don’t read from localStorage or window in server components.
❌ Don’t conditionally render or in client components.

FOUC is annoying — but totally fixable in Next.js App Router. By carefully separating your server logic from client-specific theming or rendering, you can eliminate style flashes, avoid hydration warnings, and ensure a polished user experience.