رضایت کوکی برای Next.js، Gatsby و سایتهای استاتیک: راهنمای یکپارچهسازی برای توسعهدهندگان
مسئله رضایت در سایتهای استاتیک
فریمورکهای مدرن JavaScript مانند Next.js، Gatsby و Nuxt.js یک تغییر پارادایم در نحوه ساخت و تحویل صفحات وب ایجاد کردهاند. صفحات در زمان build یا روی سرور از پیش رندر میشوند و سپس در سمت کلاینت هیدریت میشوند. این موضوع یک چالش منحصربهفرد برای رضایت کوکی ایجاد میکند: بنر رضایت باید قبل از اجرای هر اسکریپت ردیابی آماده باشد، اما خود صفحه ممکن است از قبل رندر شده و در edge کش شده باشد.
CMPهای سنتی برای صفحات PHP رندرشده در سرور یا صفحات ساده HTML طراحی شد�� بودند که در آنها سند بهصورت خطی از بالا به پایین لود میشود. در دنیای فریمورکها با code splitting، lazy loading و streaming server-side rendering، این فرضیات از بین میروند. پیادهسازی صحیح رضایت در این محیطها نیازمند درک دقیق پایپلاین رندر است.
چرا زمانبندی مهمتر از چیزی است که فکر میکنید
در یک صفحه HTML استاندارد، قرار دادن اسکریپت CMP در <head> قبل از سایر اسکریپتها کار سادهای است. در Next.js App Router یا Gatsby، وضعیت پیچیدهتر است:
- HTML از پیش رندرشده ابتدا میرسد: مرورگر HTML کامل را از CDN یا سرور دریافت میکند. اگر هر اسکریپت inline یا تگ شخص ثالثی در آن HTML جاسازی شده باشد، ممکن است قبل از اینکه منطق رضایت شما لود شود اجرا شود.
- شکاف هیدریشن: هیدریشن React بعد از paint شدن HTML انجام میشود. اگر کامپوننت رضایت شما یک کامپوننت React باشد، تا زمانی که هیدریشن کامل نشود در حالت عملیاتی وجود نخواهد داشت. در طول این شکاف، تگهای Google یا اسکریپتهای آنالیتیکس میتوانند بدون رضایت کاربر اجرا شوند.
- پیچیدگیهای کش در edge: اگر از ISR (Incremental Static Regeneration) یا edge functions استفاده کنید، HTML کش میشود. شما نمیتوانید بدون یک مکانیزم سمت کلاینت، منطق وابسته به رضایت را بهصورت داینامیک در HTML کششده تزریق کنید.
اصل کلیدی این است: رضایت باید در سطح اسکریپت برقرار شود، نه در سطح کامپوننت. یک کامپوننت React که بنر رضایت را رندر میکند، اگر فقط بعد از هیدریشن تعاملی شود، خیلی دیر است.
یکپارچهسازی با Next.js App Router
Next.js 13+ با App Router روش جدیدی برای مدیریت اسکریپتها معرفی کرده است. رویکرد پیشنهادی برای یکپارچهسازی رضایت به این صورت است:
گام ۱: لود کردن اسکریپت CMP در روت Layout
ا�� کامپوننت Script در Next.js با استراتژی beforeInteractive در layout.tsx ریشه استفاده کنید. این کار به Next.js میگوید اسکریپت را قبل از شروع هیدریشن در سند HTML اولیه تزریق کند:
استراتژی beforeInteractive حیاتی است. استراتژی پیشفرض afterInteractive اسکریپتها را بعد از هیدریشن لود میکند که برای رضایت خیلی دیر است. با beforeInteractive، اسکریپت CMP در HTML رندرشده در سرور گنجانده میشود و هنگام لود صفحه اجرا میشود.
گام ۲: تنظیم رضایت پیشفرض قبل از تگهای Google
قبل از اسنیپت Google Tag Manager یا gtag.js، یک اسکریپت inline قرار دهید که وضعیتهای پیشفرض رضایت را تنظیم کند. این تضمین میکند که حتی اگر GTM قبل از ظاهر شدن بنر CMP لود شود، به مقادیر پیشفرض «رد شده» احترام بگذارد:
این اسکریپت inline باید در <head> layout ریشه شما و قبل از اسکریپتهای CMP و GTM قر��ر بگیرد. در Next.js میتوانید برای این منظور از یک تگ معمولی <script> داخل عنصر <head> در layout استفاده کنید.
گام ۳: مدیریت تغییر مسیرها (Route Changes)
در ناوبری single-page application، اسکریپت CMP یکبار لود میشود اما تغییر مسیرها باعث رفرش کامل صفحه نمیشوند. CMP شما باید در ناوبریهای سمت کلاینت پایدار بماند. FlexyConsent این موضوع را بهصورت خودکار مدیریت میکند — پس از لود شدن، بدون نیاز به مقداردهی اولیه مجدد در تمام تغییر مسیرها فعال باقی میماند.
یکپارچهسازی با Next.js Pages Router
برای پروژههایی که هنوز از Pages Router استفاده میکنند، رویکرد مشابه است اما بهجای layout ریشه از _document.tsx استفاده میشود. اسکریپت CMP را در کامپوننت <Head> کلاس Document سفارشی خود قرار دهید. استراتژی beforeInteractive در Pages Router نیز به همین شکل کار میکند.
تفاوت کلیدی این است که _document.tsx فقط روی سرور رندر میشود، بنابراین هر منطق رضایتی که اینجا قرار میدهید تضمین میشود در payload اولیه HTML وجود داشته باشد.
یکپارچهسازی سایت استاتیک Gatsby
Gatsby در زمان build، HTML کاملاً استاتیک تولید میکند. در زمان درخواست، هیچ server-side renderingای انجام نمیشود؛ این موضوع برخی جنبهها را ساده و برخی دیگر را پیچیده میکند:
- از
gatsby-ssr.tsxاستفاده کنید تا اسکریپت CMP را در<head>تمام صفحات تزریق کنید. API مربوط بهonRenderBodyبه شما اجازه میدهد اسکریپتهایی به head اضافه کنید که در تمام فایلهای HTML استاتیک حضور خواهند داشت. - از پلاگینهای Gatsby که رضایت را lazy-load میکنند اجتناب کنید: برخی پلاگینهای جامعه، رضایت را در کامپوننتهای React میپیچند که فقط بعد از هیدریشن mount میشوند. ای�� همان شکاف زمانی است که پیشتر توضیح داده شد.
- مقادیر پیشفرض رضایت را inline قرار دهید: از
setHeadComponentsدرgatsby-ssr.tsxبرای افزودن یک اسکریپت inline که وضعیتهای پیشفرض رضایت را تنظیم میکند استفاده کنید. این اسکریپت در HTML استاتیک خواهد بود و بلافاصله اجرا میشود.
رویکرد build-time در Gatsby به این معناست که هر فایل HTML روی CDN شما شامل اسکریپت رضایت خواهد بود. این در واقع ایدهآل است — هیچ منطق سروری وجود ندارد که خراب شود یا اشتباه کش شود.
نکات مربوط به Nuxt.js
Nuxt.js (مبتنی بر Vue) الگوهای خاص خود را دارد. در Nuxt 3، از composable مربوط به useHead یا تنظیمات app head در nuxt.config.ts برای افزودن اسکریپت CMP بهصورت سراسری استفاده کنید. Nuxt از گزینه body: false (که اسکریپتها را در head قرار میدهد) و ویژگی async برای لود غیرمسدودکننده پشتیبانی میکند.
برای حالت server-side rendering در Nuxt، همان اصل اعمال میشود: اسکریپت CMP باید در پاسخ اولیه HTML باشد، نه اینکه بعد از mount شدن توسط یک کامپوننت Vue بهصورت داینامیک تزریق شود.
جلوگیری از جابهجایی چیدمان (Layout Shift)
بنرهای رضایت بهخاطر ایجاد Cumulative Layout Shift (CLS) بدنام هستند؛ معیاری از Core Web Vitals که روی رتبهبندی SEO تأثیر میگذارد. وقتی یک بنر بعد از رندر صفحه ظاهر میشود، محتوا را به پایین هل میدهد یا بهطور غیرمنتظره روی آن قرار میگیرد.
استراتژیهایی برای به حداقل رساندن CLS ناشی از بنرهای رضایت:
- استفاده از بنر پایین صفحه: بنرهایی که در پایین viewport قرار میگیرند، محتوای صفحه را جابهجا نمیکنند. این رویکرد بیشترین سازگاری را با CLS دارد.
- رزرو فضا: اگر مجبورید از بنر بالای صفحه استفاده کنید، فضای عمو��ی آن را در CSS رزرو کنید تا چیدمان صفحه قبل از رندر بنر، آن را در نظر گرفته باشد.
- اجتناب از modal overlay هنگام لود: دیوارهای رضایت تمامصفحه که بعد از رندر صفحه ظاهر میشوند، ناپایداری چیدمان ادراکشده ایجاد میکنند. اگر به چنین دیواری نیاز دارید، آن را بهعنوان بخشی از حالت اولیه صفحه رندر کنید.
- لود همگام CMP در head: وقتی CMP بهعنوان یک اسکریپت مسدودکننده رندر در head لود میشود، بنر میتواند بهعنوان بخشی از paint اولیه ظاهر شود، نه اینکه بعداً بهصورت ناگهانی پدیدار شود.
رویکرد مستقل از فریمورک FlexyConsent
FlexyConsent طوری طراحی شده که با هر فریمورکی — یا حتی بدون فریمورک — کار کند، زیرا در سطح اسکریپت عمل میکند نه در سطح کامپوننت. این موضوع از این جهت اهمیت دارد:
- یک تگ اسکریپت async واحد: تنها یک تگ
<script>در<head>کافی است. نیازی به نصب پکیجهای npm، wrapperهای مخصوص فریمورک یا تنظیمات build نیست. - مقادیر پیشفرض رضایت بلافاصله اعمال میشوند: اسکریپت، Consent Mode V2 را بهعنوان اولین اقدام خود و قبل از هر callback یا دستکاری DOM تنظیم میکند. این یعنی تگهای Google از همان میلیثانیه اول به رضایت احترام میگذارند.
- بدون وابستگی به DOM: منطق رضایت منتظر هیدریشن React، Vue یا Svelte نمیماند. این منطق مستقل از چرخه حیات فریمورک عمل میکند.
- سازگار با SSG، SSR، ISR و CSR: چون یک اسکریپت ساده است، فرقی نمیکند صفحه بهصورت استاتیک تولید شده، روی سرور رندر شده، بهصورت تدریجی بازتولید شده یا در سمت کلاینت رندر شده باشد؛ رفتار آن یکسان است.
نکته توسعهدهنده: سادهترین تست برای بررسی صحت یکپارچهسازی CMP این است که تب Network مرورگر را باز کنید، روی دامنههای Google فیلتر بگذارید و صفحه را رفرش کنید. هیچ درخواست Googleا�� نباید قبل از ظاهر شدن دستور پیشفرض رضایت در کنسول ارسال شود. اگر این اتفاق افتاد، CMP شما خیلی دیر لود میشود.
پلن رایگان FlexyConsent از تعداد نامحدود pageview پشتیبانی میکند و با Next.js، Gatsby، Nuxt، Astro، SvelteKit، Remix و HTML ساده کار میکند. روش یکپارچهسازی در همه آنها یکسان است: یک تگ اسکریپت، در جای درست.