Устранение FOUC-эффекта при использовании тёмной темы сайта в Next.js
9 сентября 2025 г.
Что такое FOUC-эффект
FOUC-эффект (Flash of Unstyled Content) – это явление, когда веб-страница отображается в стилях по умолчанию, до загрузки внешнего файла CSS или JavaScript.
Эффект чаще всего проявляется при использовании тёмной темы – сначала применяется тема по умолчанию (обычно светлая), затем происходит переход в тёмный режим, из-за чего интерфейс на мгновение мерцает.
Решение
- В корневом макете сайта устанавливаем тему по умолчанию посредством атрибута
data-theme
тегаhtml
.
import "./globals.css";
export default function RootLayout({ children }) {
return (
<html lang="ru">
<html lang="ru" data-theme="light">
<body className={`${font.className} antialiased`}>
{children}
</body>
</html>
);
}
- В дальнейшем, все стили тёмной и светлой тем будут зависеть от текущего значения этого атрибута. Например, при использовании библиотеки Tailwind CSS, можно задать глобальный модификатор стилей
dark:
при помощи директивы@custom-variant
:
@import "tailwindcss";
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
- Создаём скрипт, который будет изменять атрибут
data-theme
тегаhtml
во время загрузки сайта, в зависимости от значения, сохранённого вlocalStorage
:
const script = `
const theme = localStorage.theme;
if (theme) document.querySelector("html").dataset.theme = theme;
`;
export default function Theme() {
return <script dangerouslySetInnerHTML={{ __html: script }}></script>;
}
- Добавляем подготовленный скрипт в корневой макет сайта сразу после тега
body
:
import "./globals.css";
import Theme from "@/app/utils/theme";
export default function RootLayout({ children }) {
return (
<html lang="ru" data-theme="light">
<html lang="ru" data-theme="light" suppressHydrationWarning>
<body className={`${font.className} antialiased`}>
<Theme />
{children}
</body>
</html>
);
}
Здесь важно добавить к тегу html
атрибут suppressHydrationWarning
, чтобы подавить предупреждение React Hydration Error.
- Нестилизованный пример переключателя тёмной/светлой темы с сохранением в
localStorage
:
"use client";
import { useRef, useEffect } from "react";
export default function Toggle() {
const checkbox = useRef(null);
const toggleTheme = () => {
const theme = checkbox.current.checked ? "dark" : "light";
document.querySelector("html").dataset.theme = theme;
localStorage.theme = theme;
};
useEffect(() => {
const theme = document.querySelector("html").dataset.theme;
if (theme === "dark") checkbox.current.checked = true;
}, []);
return (
<label aria-label="Тема">
<input ref={checkbox} type="checkbox" onChange={toggleTheme} />
<span>Тёмная тема</span>
</label>
);
}
Переключатель можно вставить в любое место сайта. Обычно его располагают в правом верхнем углу.
Теперь, при загрузке сайта, он будет сразу открываться в той теме, которая сохранена в localStorage
, без FOUC-эффекта.