حالت تاریک بدون پرش رنگ در Next.js: راهحل کوکی سمت سرور
نویسنده: آرش لطیفی
پیادهسازی dark mode در Next.js App Router بدون flash سفید — با کوکی سمت سرور، بدون hydration mismatch و با حفظ رنگ تم در زبان فارسی و RTL.
خلاصه: برای حذف پرش رنگ در حالت تاریک، تم رو با کوکی ذخیره کن و در لایه سرور بخون؛
localStorageو سیستم بعد از هیدریشن همیشه یه لحظه سفیدی میسازن.
مشکل flash چیه و چرا اهمیت داره
کاربر سایتت رو باز میکنه، یه کسری از ثانیه سفید میبینه، بعد صفحه تیره میشه. اون یه کسری از ثانیه بهقدری بد بهنظر میرسه که حس «سایت بیکیفیت» میده. علتش هم سادهست: تم فقط توی مرورگر ذخیره شده (localStorage یا کوکی چند ساعته)، پس سرور هیچی ازش خبر نداره و HTML رو با تم پیشفرض رندر میکنه. بعد useEffect اجرا میشه و تم عوض میشه.
اگه قبلاً با مرز سرور و کلاینت درگیر بودی، آلودگی RSC و جای درست use client زمینهی این بحثه. اینجا میریم سراغ راهحل قطعی: تم، کوکی است و کوکی در هر دو لایه در دسترس است.
راهحل: کوکی + کلاس روی <html>
در App Router، تم را در html بهشکل کلاس میگذاریم و در همان فایل layout، کوکی را با cookies() میخوانیم. چون این کار در سرور انجام میشود، HTML اولیه از قبل تم درست را دارد.
// app/layout.tsx
import { cookies } from "next/headers";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const theme = (await cookies()).get("theme")?.value ?? "light";
return (
<html lang="fa" dir="rtl" className={theme}>
<body>{children}</body>
</html>
);
}
و استایل پایه که رنگها را به متغیر تبدیل میکند:
کلاس dark را روی html گذاشتهایم نه روی body، چون بعضی کامپوننتها (مودال، تولتیپ) به body محدود میمانند و با کلاس روی body محصور میشوند.
تغییر تم و نوشتن کوکی
تغییر تم باید در سرور انجام شود تا بلافاصله در HTML بعدی اعمال شود. سادهترین راه، یک Server Action است:
و کامپوننت کلاینتی که فقط دکمه را رندر میکند:
تم اولیه از سمت سرور، بدون hydration mismatch
اگر میخواهی تم پیشفرض مهمانها هم با تنظیمات سیستم هماهنگ باشد، از prefers-color-scheme در کوکی و یک اسکریپت کوچک قفل استفاده کن:
اگر هم از کوکی میخوانی و هم اسکریپت اجرا میکنی، هر دو رو نگه دار: کوکی برای رندر سرور و اسکریپت برای بازخورد فوری در همان فریم اول. اگر از گیتبخش و میدیلور همراه استفاده میکنی، راهنمای میدیلور و احراز هویت تکمیل خوبی است. برای اینکه رندر سرورت کش نشود و تم قدیمی برنگردد، کش و PPR در Next 15 را هم ببین.
سه راه اصلی برای خواندن تم قبل از رندر، با این موازنه:
| روش | پرتاب در فریم اول؟ | نیاز به جاوااسکریپت سمت کلاینت | مشکل اصلی |
|---|---|---|---|
| localStorage | نه | بله، برای خواندن | سرور نمیبیندش |
| کوکی + رندر سمت سرور | بله | فقط برای تعویض تم | باید سیاست کش را هماهنگ کنی |
| اسکریپت اینلاین قبل از رنگآمیزی | بله | بله | به کوکی یا متغیر سرور نیاز دارد |
قانون ساده: هر چیزی که باید در فریم اول درست باشد، باید در HTML اولیه یا کوکیِ همان درخواست باشد.
نکات ریز ولی مهم
maxAgeبگذار تا کوکی منقضی نشود؛ بدون آن تم بعد از بستن مرورگر میپرد.- در
transitionانیمیشن بگذار ولی رویbackground-imageنه، چون در موبایل کند میشود. - در پروژههای فارسی، رنگهای متغیر را با واحد rem تعریف کن تا با فونت و zoom هماهنگ بماند. اصول RTL و بهینهسازی فارسی را در راهنمای RTL و Next.js 15 جمع کردهام.
color-schemeرا هم روی هر دو تم ست کن تا اسکرولبار و فرمهای مرورگر هماهنگ شوند.
چکلیست پیادهسازی
- [ ] کوکی
themeباpathوmaxAgeست شده - [ ] تم در
layout.tsxباcookies()خوانده و روی<html>اعمال شده - [ ] متغیرهای رنگ در CSS تعریف و در هر دو تم مقداردهی شده
- [ ] هیچ
localStorageباقی نمانده که تم را برگرداند - [ ] با
revalidatePathیا رندر مجدد، HTML بهروز شده - [ ]
color-schemeبرای هر دو تم تنظیم شده - [ ] روی موبایل و دسکتاپ تست شده که پرش رنگ ندارد
سوالات پرتکرار
Q: چرا با localStorage این مشکل پیش میاد؟
چون سرور به localStorage دسترسی ندارد؛ اول HTML با تم پیشفرض رندر میشود و بعد از هیدریشن تم عوض میشود. کوکی در هر دو محیط دیده میشود، پس سرور از همان فریم اول تم درست را مینویسد.
Q: اگر کاربر وارد نشده باشد کوکی کی میسازد؟
مهمانها هم کوکی میگیرند، چون تم در سمت کلاینت انتخاب میشود و سرور آن را در پاسخ بعدی ذخیره میکند. برای همگامسازی تم بین دستگاهها باید در دیتابیس یا پروفایل کاربر ذخیره شود.
Q: آیا revalidatePath لازم است؟
اگر تم فقط در کوکی است و هر رندر آن را میخواند، اکثر اوقات لازم نیست؛ ولی وقتی صفحه با کش یا PPR نمایش داده میشود، بیآن ممکن است نسخه کششده با تم قبلی برگردد.
منابع
- مرجع
cookiesدر مستندات Next.js — خواندن و نوشتن کوکی در Server Component و Server Action - مخزن next-themes — کتابخانهای که مشکل flash و هماهنگی هیدریشن را حل میکند
- پیادهسازی تم تاریک با next-themes در App Router — راهنمای مستقل برای همین مشکل
- پرسش و پاسخ در r/nextjs دربارهی حذف پرش تم تاریک — تجربهی کاربران و بدهبستان با راهحلهای قدیمی
- مرجع hydration در React — چرا اخMismatch رندر سمت سرور و کلاینت پرچم قرمز است