<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kaylee</title>
    <description>The latest articles on DEV Community by Kaylee (@kaylee).</description>
    <link>https://dev.to/kaylee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3836828%2F003dd576-d02f-4c4a-9381-6cea2b8486b3.jpg</url>
      <title>DEV Community: Kaylee</title>
      <link>https://dev.to/kaylee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaylee"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Kaylee</dc:creator>
      <pubDate>Wed, 19 Aug 2026 10:48:57 +0000</pubDate>
      <link>https://dev.to/kaylee/-2k5b</link>
      <guid>https://dev.to/kaylee/-2k5b</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/kaylee/should-you-build-your-own-cookie-banner-build-vs-buy-guide-56o2" class="crayons-story__hidden-navigation-link"&gt;Should You Build Your Own Cookie Banner? Build vs Buy Guide&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/kaylee" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3836828%2F003dd576-d02f-4c4a-9381-6cea2b8486b3.jpg" alt="kaylee profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/kaylee" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Kaylee
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Kaylee
                
                
              
              &lt;div id="story-author-preview-content-4350756" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/kaylee" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3836828%2F003dd576-d02f-4c4a-9381-6cea2b8486b3.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Kaylee&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/kaylee/should-you-build-your-own-cookie-banner-build-vs-buy-guide-56o2" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 8&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/kaylee/should-you-build-your-own-cookie-banner-build-vs-buy-guide-56o2" id="article-link-4350756"&gt;
          Should You Build Your Own Cookie Banner? Build vs Buy Guide
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nextjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nextjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/privacy"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;privacy&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/kaylee/should-you-build-your-own-cookie-banner-build-vs-buy-guide-56o2" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/kaylee/should-you-build-your-own-cookie-banner-build-vs-buy-guide-56o2#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Should You Build Your Own Cookie Banner? Build vs Buy Guide</title>
      <dc:creator>Kaylee</dc:creator>
      <pubDate>Sat, 08 Aug 2026 23:17:40 +0000</pubDate>
      <link>https://dev.to/kaylee/should-you-build-your-own-cookie-banner-build-vs-buy-guide-56o2</link>
      <guid>https://dev.to/kaylee/should-you-build-your-own-cookie-banner-build-vs-buy-guide-56o2</guid>
      <description>&lt;p&gt;&lt;em&gt;Should you build your own cookie banner? Compare custom code with a consent platform for React and Next.js, including the maintenance costs and tradeoffs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Building your own cookie banner can look simple at first. You show a notice, store a choice, and hide the banner on the next visit.&lt;/p&gt;

&lt;p&gt;The harder question is what the banner needs to control. A production cookie banner may need to block third-party scripts, remember preferences, support withdrawal, reflect regional rules, and keep records for accountability. For many React and Next.js teams, a maintained consent layer is a better default than a fully custom banner because it reduces infrastructure while keeping consent inside the application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article gives implementation information and general legal context. It is not legal advice. Ask counsel to review your consent text, regional behavior, vendor list, retention rules, and record-keeping before release.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you should decide
&lt;/h2&gt;

&lt;p&gt;By the end, you should be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decide when a custom cookie banner is reasonable.&lt;/li&gt;
&lt;li&gt;Explain why a cookie banner becomes consent infrastructure.&lt;/li&gt;
&lt;li&gt;Compare custom code, developer-first CMPs, dashboard-first CMPs, and internal platforms.&lt;/li&gt;
&lt;li&gt;Plan backend consent persistence when auditability matters.&lt;/li&gt;
&lt;li&gt;Evaluate c15t as one option for a modern, developer-owned consent setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Short answer: build your own cookie banner only if the requirements are narrow and your team is ready to maintain the logic over time. Use a consent platform when you need script gating, preference management, regional behavior, or durable records. This guide uses c15t as a concrete example because it is designed for modern JavaScript applications and supports hosted, self-hosted, and browser-only deployments. c15t is the developer-first consent management platform, while Inth provides its managed hosted deployment for teams that do not want to operate the backend themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why teams build their own cookie banner
&lt;/h2&gt;

&lt;p&gt;Teams rarely set out to own privacy infrastructure. They build a cookie banner because the alternatives feel worse.&lt;/p&gt;

&lt;p&gt;Legacy CMPs often arrive as hosted scripts and dashboards that sit outside the application. They can be hard to style, difficult to test, and awkward in React, Next.js, Astro, Remix, and other component-based stacks. A developer sees a large script tag, a generic modal, and a consent state that is not part of the app. A custom banner starts to look like the cleaner option.&lt;/p&gt;

&lt;p&gt;That instinct is understandable. Modern apps need control over rendering, hydration, routing, performance, and script loading. The mistake is assuming the banner is the hard part.&lt;/p&gt;

&lt;h2&gt;
  
  
  A cookie banner is more than a popup
&lt;/h2&gt;

&lt;p&gt;A real cookie banner has to answer several questions before any marketing or analytics code runs.&lt;/p&gt;

&lt;p&gt;What categories does the site use? Which vendors belong to each category? Which scripts can load before consent? How can visitors change their choice later? Does consent behavior need to differ by country or state? Where does the app store the record?&lt;/p&gt;

&lt;p&gt;In EU and UK contexts, non-essential cookies and similar technologies generally require consent before storage or access, unless an exception applies. The &lt;a href="https://www.edpb.europa.eu/contact/frequently-asked-questions_en" rel="noopener noreferrer"&gt;European Data Protection Board&lt;/a&gt; identifies technically necessary cookies as the main exception. The &lt;a href="https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-the-use-of-storage-and-access-technologies/" rel="noopener noreferrer"&gt;UK ICO’s storage and access technology guidance&lt;/a&gt; explains the corresponding PECR requirements and how consent relates to the UK GDPR.&lt;/p&gt;

&lt;p&gt;That changes the engineering task. The system must stop the source of tracking before it runs. Deleting cookies later is not enough, and it is often impossible from your site because third-party scripts can set cookies on their own domains, use httpOnly cookies, or keep state in memory.&lt;/p&gt;

&lt;p&gt;A consent layer needs to operate at the script and resource boundary. &lt;a href="https://c15t.com/docs/frameworks/react/concepts/cookie-management" rel="noopener noreferrer"&gt;c15t’s cookie-management documentation&lt;/a&gt; makes an important distinction: the tool does not manage every cookie directly. It controls which scripts, iframes, and network requests may load, because those resources are usually what set the cookies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden maintenance cost of a custom cookie banner
&lt;/h2&gt;

&lt;p&gt;A custom cookie banner gives you maximum control. It also gives you maximum responsibility.&lt;/p&gt;

&lt;p&gt;At minimum, your team may need to build and maintain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consent state storage.&lt;/li&gt;
&lt;li&gt;Purpose or category preferences.&lt;/li&gt;
&lt;li&gt;Script gating before analytics, ads, pixels, and tag managers load.&lt;/li&gt;
&lt;li&gt;Preference updates and consent withdrawal.&lt;/li&gt;
&lt;li&gt;A way to reopen the preference dialog.&lt;/li&gt;
&lt;li&gt;Accessibility and keyboard behavior.&lt;/li&gt;
&lt;li&gt;Translation and localization.&lt;/li&gt;
&lt;li&gt;Region-aware defaults.&lt;/li&gt;
&lt;li&gt;Consent policy versioning.&lt;/li&gt;
&lt;li&gt;Backend records for accountability or support workflows.&lt;/li&gt;
&lt;li&gt;Tests that prove scripts do not load too early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browser storage may be enough for a small site, but it may not be enough when a privacy, support, or legal team needs to understand what a visitor chose, when they chose it, which policy version they saw, and whether the choice changed later.&lt;/p&gt;

&lt;p&gt;GDPR accountability does not prescribe one database schema for cookie consent. It does require controllers to be able to demonstrate compliance with data-protection principles under &lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32016R0679" rel="noopener noreferrer"&gt;GDPR Article 5(2)&lt;/a&gt;. In practice, backend persistence and audit logs can become part of the consent design, especially for higher-risk sites, regulated teams, or sites with many third-party vendors.&lt;/p&gt;

&lt;p&gt;If you build your own banner, your team owns that record-keeping model too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build vs buy at a glance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;UI control&lt;/th&gt;
&lt;th&gt;Script gating&lt;/th&gt;
&lt;th&gt;Consent records&lt;/th&gt;
&lt;th&gt;Ongoing ownership&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Custom build&lt;/td&gt;
&lt;td&gt;Narrow, stable requirements&lt;/td&gt;
&lt;td&gt;Maximum&lt;/td&gt;
&lt;td&gt;You build it&lt;/td&gt;
&lt;td&gt;You design and operate it&lt;/td&gt;
&lt;td&gt;Your engineering team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;c15t, a developer-first CMP&lt;/td&gt;
&lt;td&gt;Modern JavaScript products and enterprise application estates&lt;/td&gt;
&lt;td&gt;High, including headless UI&lt;/td&gt;
&lt;td&gt;Framework-aware loaders&lt;/td&gt;
&lt;td&gt;Managed hosting through Inth, self-hosted, or browser-only&lt;/td&gt;
&lt;td&gt;Shared between engineering, privacy teams, and the product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard-first CMP&lt;/td&gt;
&lt;td&gt;Organisations centring scanning, dashboards, procurement, and privacy workflows&lt;/td&gt;
&lt;td&gt;Varies by vendor&lt;/td&gt;
&lt;td&gt;Usually included&lt;/td&gt;
&lt;td&gt;Usually included&lt;/td&gt;
&lt;td&gt;Vendor plus privacy and implementation teams&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is a starting point, not a compliance decision. Vendor count, jurisdictions, internal workflows, and legal advice can change the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A middle path: app-native consent infrastructure
&lt;/h2&gt;

&lt;p&gt;A framework-aware consent platform offers a middle path between a generic hosted widget and a hand-built system. You keep consent inside the application architecture without starting from an empty file. c15t is one open-source option built around that model.&lt;/p&gt;

&lt;p&gt;For example, the &lt;a href="https://c15t.com/docs/frameworks/next/quickstart" rel="noopener noreferrer"&gt;c15t Next.js integration&lt;/a&gt; provides &lt;code&gt;@c15t/nextjs&lt;/code&gt;, a &lt;code&gt;ConsentManagerProvider&lt;/code&gt;, prebuilt banner and dialog components, App Router setup, hosted mode, offline mode, and server-side startup options. Its React integration exposes providers, hooks, components, and headless paths. The &lt;a href="https://c15t.com/docs/comparison" rel="noopener noreferrer"&gt;comparison guide&lt;/a&gt; explains where this approach fits relative to hosted widgets and custom implementations.&lt;/p&gt;

&lt;p&gt;A basic Next.js setup looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ConsentBanner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ConsentDialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ConsentManagerProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@c15t/nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ConsentProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentManagerProvider&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hosted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;backendURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-instance.c15t.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;consentCategories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;necessary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marketing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentBanner&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentDialog&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConsentManagerProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This initializes the consent provider, renders the banner and preference dialog, and makes consent state available to the application. It does not automatically discover every tracking script already on the site. You still need to register consent-gated scripts or remove tracking snippets that bypass the consent layer.&lt;/p&gt;

&lt;p&gt;From there, the UI can be customised with props, design tokens, slots, CSS variables, &lt;code&gt;noStyle&lt;/code&gt;, or headless components. A team can stay close to the default interface or replace the visual layer when the product needs a fully custom consent experience.&lt;/p&gt;

&lt;p&gt;That is the important difference from many legacy CMP setups: developers do not have to choose between a generic hosted widget and a hand-rolled consent system. The consent foundation can remain part of the framework and codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Script gating is where custom banners get risky
&lt;/h2&gt;

&lt;p&gt;Custom banners often fail at the same point: the UI stores a choice, but scripts still load somewhere else.&lt;/p&gt;

&lt;p&gt;That can happen when analytics are in app/layout.tsx, a marketing tag is injected by a CMS, a pixel loads through a tag manager, or a product analytics SDK initializes in a client component. If any of those paths bypass the banner, the banner is no longer the source of truth.&lt;/p&gt;

&lt;p&gt;A central script loader keeps that behavior in one place. With the &lt;a href="https://c15t.com/docs/frameworks/next/script-loader" rel="noopener noreferrer"&gt;c15t script loader&lt;/a&gt;, for example, you declare scripts in provider options, assign each one to a consent category, and let the consent state control its lifecycle. The same layer can govern analytics, pixels, tag managers, iframes, and vendor scripts without scattering one-off guards across the application.&lt;/p&gt;

&lt;p&gt;For concrete examples, see &lt;a href="https://inth.com/how-to-integrate-meta-pixel-nextjs-c15t" rel="noopener noreferrer"&gt;how to integrate Meta Pixel in Next.js&lt;/a&gt; and &lt;a href="https://inth.com/how-to-add-posthog-to-next-js-with-gdpr-aware-consent" rel="noopener noreferrer"&gt;how to add PostHog to Next.js with consent-aware loading&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent may need backend persistence
&lt;/h2&gt;

&lt;p&gt;A browser-only banner can remember a visitor's choice on that device. That may be enough for a low-risk site with a small set of scripts and no operational need to inspect consent history.&lt;/p&gt;

&lt;p&gt;If the business needs durable records, browser storage is weaker. It can be cleared, it gives support and privacy teams no record to inspect, and it does not create server-side visibility into consent state.&lt;/p&gt;

&lt;p&gt;Consent platforms can provide backend persistence for this reason. With c15t, teams can use a hosted backend, &lt;a href="https://c15t.com/docs/self-host/quickstart" rel="noopener noreferrer"&gt;self-host it&lt;/a&gt;, or keep consent in the browser with offline mode when durable records are unnecessary. The right choice depends on auditability, infrastructure, and data-governance requirements.&lt;/p&gt;

&lt;p&gt;The engineering point is simple: if auditability matters, backend persistence should be part of the cookie banner decision from the start. You still need legal review for what you store, how long you store it, and how you disclose it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When another approach may fit
&lt;/h2&gt;

&lt;p&gt;No single operating model fits every team. c15t is a developer-first CMP, not a small-team-only alternative to enterprise CMPs. Through managed hosting on Inth or self-hosting, it can support enterprise requirements such as regional policy control, durable consent records, and consistent integration across multiple applications. Some organisations will prefer a dashboard-first CMP when scanning, procurement, and privacy-team workflows are the primary control plane. A custom internal platform may fit companies that already operate privacy infrastructure across many products.&lt;/p&gt;

&lt;p&gt;The distinction is the operating model, not company size. For a React or Next.js estate, c15t can be the enterprise choice when engineering teams need application-native consent state and script gating, with deployment options ranging from offline mode to managed Inth or self-hosted backends. Privacy and legal teams can retain governance through policies and durable records where required.&lt;/p&gt;

&lt;p&gt;A custom cookie banner can still make sense when the scope is small and stable. For example, a static site with no non-essential tracking may need only a short notice or no banner at all, depending on the technologies used and the jurisdictions involved. If your team still chooses custom, treat it like infrastructure: test that non-essential requests do not happen before consent, keep vendor categories under review, and document how visitors can change or withdraw their choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cookie banner build vs buy checklist
&lt;/h2&gt;

&lt;p&gt;Use c15t when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You use React, Next.js, or another modern JavaScript framework.&lt;/li&gt;
&lt;li&gt;You want a custom-looking banner without custom consent infrastructure.&lt;/li&gt;
&lt;li&gt;You need to gate analytics, pixels, tag managers, iframes, or vendor scripts.&lt;/li&gt;
&lt;li&gt;You need backend consent records or audit history through a hosted or self-hosted deployment.&lt;/li&gt;
&lt;li&gt;Your requirements are simple and browser-only storage is sufficient through offline mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider another approach when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your primary control plane must be a privacy-team dashboard with built-in scanning or procurement workflows. Compare that operating model with c15t and Inth rather than assuming enterprise scale determines the fit.&lt;/li&gt;
&lt;li&gt;You need broad non-JavaScript platform coverage beyond the available integration paths. A dashboard-first CMP or internal platform may fit better.&lt;/li&gt;
&lt;li&gt;You have no non-essential cookies, scripts, or similar technologies. Confirm with counsel, because a banner may not be needed.&lt;/li&gt;
&lt;li&gt;You already operate a company-wide privacy platform. Custom or internal platform integration may fit better.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main question is not whether your team can build a banner. Most teams can. The question is whether they want to maintain consent infrastructure every time the app, vendors, privacy policy, or regulatory requirements change.&lt;/p&gt;

&lt;p&gt;For many modern JavaScript teams, the answer is no. They want the control that made custom builds attractive, backed by a consent layer designed for their stack. c15t is one way to get that balance without outsourcing the entire experience to a generic widget.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do next
&lt;/h2&gt;

&lt;p&gt;Treat cookie-banner build vs buy as an architecture decision, not a UI decision. Start by inventorying every script and iframe, deciding whether browser-only consent is sufficient, and documenting who will maintain the system.&lt;/p&gt;

&lt;p&gt;If marketing or analytics tags already run on the site, audit those loading paths before adding more vendor code. Want to try c15t? &lt;a href="https://inth.com/dashboard/sign-up" rel="noopener noreferrer"&gt;Start with managed c15t on Inth&lt;/a&gt; or follow the &lt;a href="https://c15t.com/docs/frameworks/next/quickstart" rel="noopener noreferrer"&gt;c15t Next.js quickstart&lt;/a&gt; to integrate it directly.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>privacy</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to add PostHog to Next.js with GDPR-aware consent</title>
      <dc:creator>Kaylee</dc:creator>
      <pubDate>Thu, 16 Jul 2026 17:03:04 +0000</pubDate>
      <link>https://dev.to/kaylee/how-to-add-posthog-to-nextjs-with-gdpr-aware-consent-42ac</link>
      <guid>https://dev.to/kaylee/how-to-add-posthog-to-nextjs-with-gdpr-aware-consent-42ac</guid>
      <description>&lt;p&gt;&lt;em&gt;Implement PostHog in a Next.js App Router project with c15t, then use a GDPR-aware consent setup that waits for measurement consent before loading analytics.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Analytics code should match the consent model of the site that loads it. A basic PostHog install can be enough for an internal tool, but an EU-facing app may need to stop PostHog from loading until the user grants measurement consent. c15t handles that boundary in Next.js by tying the PostHog script to the &lt;code&gt;measurement&lt;/code&gt; consent category.&lt;/p&gt;

&lt;p&gt;This guide uses the c15t PostHog script helper first. That path keeps PostHog loading, consent updates, revocation, and script removal in one place. It also gives you a stricter EU configuration with &lt;code&gt;region: 'eu'&lt;/code&gt; and &lt;code&gt;loadMode: 'after-consent'&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article gives implementation and legal information, not legal advice. Ask counsel to review the consent copy, privacy notice, PostHog settings, and regional behavior before release.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;By the end, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install c15t in a Next.js App Router project.&lt;/li&gt;
&lt;li&gt;Register PostHog through &lt;code&gt;@c15t/scripts/posthog&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep PostHog out of the page until &lt;code&gt;measurement&lt;/code&gt; consent is granted.&lt;/li&gt;
&lt;li&gt;Track client events without throwing before PostHog loads.&lt;/li&gt;
&lt;li&gt;Explain what happens when measurement consent is withdrawn.&lt;/li&gt;
&lt;li&gt;Decide when cookieless PostHog tracking is a better fit.&lt;/li&gt;
&lt;li&gt;Explain why EU analytics setups often need a stricter consent design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the consent lifecycle works
&lt;/h2&gt;

&lt;p&gt;The strict setup has one rule: PostHog does not load until the user grants &lt;code&gt;measurement&lt;/code&gt; consent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User visits
    ↓
c15t initializes
    ↓
Consent given for measurement?
   ├─ No  → PostHog never loads
   └─ Yes → c15t injects PostHog → events start flowing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app should not call &lt;code&gt;posthog.init()&lt;/code&gt; directly in this model. c15t owns the script lifecycle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Loads PostHog immediately, outside the c15t script lifecycle.&lt;/span&gt;
&lt;span class="nx"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_POSTHOG_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;api_host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://eu.i.posthog.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register PostHog as a c15t script instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ c15t loads PostHog only after measurement consent.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scripts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_POSTHOG_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loadMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after-consent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That difference matters. A direct &lt;code&gt;posthog.init()&lt;/code&gt; can create a script request before c15t has a chance to evaluate consent. The c15t helper waits for the consent state first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the PostHog loading model
&lt;/h2&gt;

&lt;p&gt;c15t exposes two PostHog integration patterns.&lt;/p&gt;

&lt;p&gt;Use the script helper for a new Next.js app. The helper loads PostHog's bootstrap script, calls &lt;code&gt;posthog.init()&lt;/code&gt; for you, and synchronizes measurement consent with PostHog's opt-in and opt-out APIs. You configure it with &lt;code&gt;posthog({ id, region, loadMode })&lt;/code&gt; and pass the result to &lt;code&gt;ConsentManagerProvider&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use the SDK pattern when your app already loads &lt;code&gt;posthog-js&lt;/code&gt;. In that setup, your app owns SDK initialization and c15t only synchronizes consent.&lt;/p&gt;

&lt;p&gt;The script helper has three loading modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;after-consent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;c15t does not request PostHog until &lt;code&gt;measurement&lt;/code&gt; consent is granted.&lt;/td&gt;
&lt;td&gt;Your policy requires no PostHog script or network request before consent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;always&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;c15t loads PostHog early and forwards consent changes to PostHog.&lt;/td&gt;
&lt;td&gt;You intentionally use PostHog cookieless behavior after rejection.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;c15t creates an inert integration with no PostHog request.&lt;/td&gt;
&lt;td&gt;You need an environment flag or temporary rollout switch.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This guide starts with &lt;code&gt;after-consent&lt;/code&gt; because the behavior is easy to test: no consent, no PostHog script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the packages
&lt;/h2&gt;

&lt;p&gt;Start from an existing Next.js App Router project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @c15t/nextjs @c15t/scripts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the c15t stylesheet in your global CSS file. You can skip this only if you use the headless API or fully custom styling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* app/globals.css */&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@c15t/nextjs/styles.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the public values the browser needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.local&lt;/span&gt;
&lt;span class="nv"&gt;NEXT_PUBLIC_C15T_BACKEND_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://your-instance.c15t.dev"&lt;/span&gt;
&lt;span class="nv"&gt;NEXT_PUBLIC_POSTHOG_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PostHog project API key is a browser key. Keep it scoped to the right PostHog project and environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the c15t provider
&lt;/h2&gt;

&lt;p&gt;Create a client component for the consent UI and the PostHog script list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/consent-manager/provider.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ConsentBanner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ConsentDialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ConsentManagerProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@c15t/nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posthog&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@c15t/scripts/posthog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backendURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_C15T_BACKEND_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posthogKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_POSTHOG_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scripts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posthogKey&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nf"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posthogKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;loadMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after-consent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ConsentManagerClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;backendURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing NEXT_PUBLIC_C15T_BACKEND_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentManagerProvider&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hosted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;backendURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;consentCategories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;necessary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marketing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentBanner&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentDialog&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConsentManagerProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;region: 'eu'&lt;/code&gt; option keeps PostHog's API, UI, and bootstrap hosts aligned with PostHog Cloud EU. The &lt;code&gt;loadMode: 'after-consent'&lt;/code&gt; option stops c15t from injecting PostHog until the user grants &lt;code&gt;measurement&lt;/code&gt; consent.&lt;/p&gt;

&lt;p&gt;If your PostHog project uses Cloud US, change the region.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posthogKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;loadMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after-consent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hosted mode is the recommended production setup in the c15t Next.js docs. It lets the backend resolve jurisdiction and policy, keep durable consent records, and recover from temporary network failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mount the provider in the app root
&lt;/h2&gt;

&lt;p&gt;Keep the client boundary in one file and export a small wrapper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/consent-manager/index.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ConsentManagerClient&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ConsentManager&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentManagerClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConsentManagerClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrap the app from &lt;code&gt;app/layout.tsx&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./globals.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ConsentManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/consent-manager&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentManager&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConsentManager&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider should sit above the routes that need consent state or managed scripts. Do not add a separate &lt;code&gt;next/script&lt;/code&gt; tag for PostHog, because that bypasses c15t's lifecycle for the vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Track events safely
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;loadMode: 'after-consent'&lt;/code&gt;, &lt;code&gt;window.posthog&lt;/code&gt; is unavailable until measurement consent is granted and the script loads. Optional chaining prevents a runtime error during that pre-consent window.&lt;/p&gt;

&lt;p&gt;Add a small type if you do not already import PostHog types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// types/posthog.d.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then read c15t consent state before calling PostHog.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/signup-button.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useConsentManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@c15t/nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SignupButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useConsentManager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trackSignupClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup_clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;homepage_hero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;trackSignupClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign up&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is strict. If the user rejects measurement, PostHog stays absent and the event is not sent. There is no app-level event queue in this example because the call exits before PostHog exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when consent is withdrawn
&lt;/h2&gt;

&lt;p&gt;Consent withdrawal is different from a first rejection.&lt;/p&gt;

&lt;p&gt;If a user rejects measurement on the first visit, PostHog has not loaded in the &lt;code&gt;after-consent&lt;/code&gt; setup. c15t records the choice, and the page can continue without a reload.&lt;/p&gt;

&lt;p&gt;If a user previously granted measurement and later withdraws it, c15t reloads the page by default. The reload creates a fresh JavaScript context. On the next load, c15t reads the updated consent state and does not inject PostHog.&lt;/p&gt;

&lt;p&gt;That default reload matters because JavaScript cannot reliably clean up every trace of a third-party script after it has run. Some cookies may be &lt;code&gt;httpOnly&lt;/code&gt;, scoped to another domain, or stored outside normal cookies. A loaded SDK can also keep timers, event listeners, and in-memory state until the page unloads.&lt;/p&gt;

&lt;p&gt;For the strict &lt;code&gt;after-consent&lt;/code&gt; setup, expect this behavior:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Does PostHog stop?&lt;/td&gt;
&lt;td&gt;Yes. c15t stops loading it after the reload because &lt;code&gt;measurement&lt;/code&gt; consent is no longer present.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Are cookies removed?&lt;/td&gt;
&lt;td&gt;c15t gates the script that creates most third-party cookies. It does not promise to delete every cookie or storage entry a vendor may have set before withdrawal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the page reloaded?&lt;/td&gt;
&lt;td&gt;Yes, by default, when consent is revoked after scripts have already run. c15t documents this as the reliable cleanup path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What happens to queued events?&lt;/td&gt;
&lt;td&gt;With the guarded &lt;code&gt;after-consent&lt;/code&gt; example, rejected-consent calls are not queued. In other PostHog modes, pending events before the bootstrap or consent sync may be dropped.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can change revocation reload behavior with &lt;code&gt;reloadOnConsentRevoked: false&lt;/code&gt;, but only do that if you have your own cleanup strategy for every loaded SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the consent boundary
&lt;/h2&gt;

&lt;p&gt;Run the app locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a clean browser profile and open DevTools.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Network tab and filter for &lt;code&gt;posthog&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Refresh the page before making a consent choice.&lt;/li&gt;
&lt;li&gt;Confirm that no &lt;code&gt;posthog.js&lt;/code&gt; or PostHog API request appears.&lt;/li&gt;
&lt;li&gt;Accept the &lt;code&gt;measurement&lt;/code&gt; category in the c15t banner or dialog.&lt;/li&gt;
&lt;li&gt;Confirm that the PostHog script loads.&lt;/li&gt;
&lt;li&gt;Click the UI that calls &lt;code&gt;window.posthog?.capture()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Confirm that the event appears in PostHog.&lt;/li&gt;
&lt;li&gt;Reopen privacy preferences and withdraw &lt;code&gt;measurement&lt;/code&gt; consent.&lt;/li&gt;
&lt;li&gt;Confirm that the page reloads by default.&lt;/li&gt;
&lt;li&gt;Confirm that PostHog does not load after the reload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The development override in the provider forces a Germany-like test path. Keep it scoped to development or remove it before production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why EU setups need more care
&lt;/h2&gt;

&lt;p&gt;EU analytics work often has two layers.&lt;/p&gt;

&lt;p&gt;The first layer is device storage and access. Article 5(3) of the ePrivacy Directive covers storing information on, or accessing information from, a user's terminal equipment. Consent is commonly required unless a narrow exception applies, such as technical transmission or a service that is strictly necessary and explicitly requested by the user. The EDPB's guidance on Article 5(3) explains that the rule is not limited to traditional cookies.&lt;/p&gt;

&lt;p&gt;The second layer is personal-data processing. GDPR can apply when analytics events include personal data or online identifiers. If it applies, you need to assess lawful basis, transparency, purpose limitation, data minimization, retention, security, and user rights. If you rely on consent under GDPR, the consent needs to be freely given, specific, informed, unambiguous, and as easy to withdraw as to give.&lt;/p&gt;

&lt;p&gt;This is why an EU-facing PostHog setup can be more complicated than a normal script install. A script request can happen before the user clicks a banner. A product analytics event can include URLs, referrers, UTM parameters, browser details, account identifiers, and IP-derived signals depending on your settings.&lt;/p&gt;

&lt;p&gt;For a stricter EU design, keep the PostHog helper on this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posthogKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;loadMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after-consent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That configuration supports two practical requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostHog Cloud uses the EU region.&lt;/li&gt;
&lt;li&gt;c15t does not inject the PostHog script until &lt;code&gt;measurement&lt;/code&gt; consent is granted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not describe this as making the app GDPR compliant. It supports a stricter consent posture. Your team still needs the right notice text, PostHog data settings, retention rules, vendor disclosures, transfer analysis, and user-rights process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cookieless mode only when it matches your policy
&lt;/h2&gt;

&lt;p&gt;PostHog can also collect cookieless analytics. In c15t, that usually means &lt;code&gt;loadMode: 'always'&lt;/code&gt;. PostHog loads on page start, c15t syncs consent through PostHog APIs, and PostHog can switch to cookieless capture after a rejection when your PostHog project supports it.&lt;/p&gt;

&lt;p&gt;Use this only when legal review and your privacy notice allow PostHog to load before measurement consent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/consent-manager/provider.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ConsentManagerProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@c15t/nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posthog&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@c15t/scripts/posthog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scripts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_POSTHOG_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loadMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;always&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;initOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;cookieless_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on_reject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ConsentProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConsentManagerProvider&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hosted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;backendURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_C15T_BACKEND_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConsentManagerProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before using &lt;code&gt;cookieless_mode&lt;/code&gt;, enable Cookieless server hash mode in PostHog under Project Settings &amp;gt; Web analytics. The c15t PostHog docs state that PostHog ignores cookieless events unless that project setting is enabled.&lt;/p&gt;

&lt;p&gt;Cookieless mode reduces browser persistence. It does not remove the need to assess notice, legal basis, data minimization, retention, transfers, or whether a pre-consent network request is allowed for your site.&lt;/p&gt;

&lt;p&gt;Also expect analytics edge cases at the consent boundary. PostHog may start a new session when a user moves between cookieless and cookie-based capture, which can split pre-consent and post-consent activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you already load &lt;code&gt;posthog-js&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For an app that already uses &lt;code&gt;posthog-js&lt;/code&gt;, keep the SDK and let c15t synchronize consent. The c15t PostHog docs recommend an initial sync after c15t resolves consent, then &lt;code&gt;subscribeToConsentChanges()&lt;/code&gt; for real preference changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/posthog-consent.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getOrCreateConsentRuntime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;c15t&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;posthog&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posthog-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncPostHogMeasurementConsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasMeasurementConsent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasMeasurementConsent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opt_in_capturing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opt_out_capturing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setupPostHogConsentSync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getOrCreateConsentRuntime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hosted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;onBannerFetched&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;syncPostHogMeasurementConsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consentStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consentStore&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeToConsentChanges&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;allowedCategories&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;syncPostHogMeasurementConsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;allowedCategories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not use &lt;code&gt;posthog.has_opted_in_capturing()&lt;/code&gt; or &lt;code&gt;posthog.has_opted_out_capturing()&lt;/code&gt; to decide whether to show your banner. The c15t PostHog docs warn that &lt;code&gt;has_opted_in_capturing()&lt;/code&gt; can return &lt;code&gt;true&lt;/code&gt; while consent is still pending in recent PostHog versions. Use c15t as the source of truth for banner state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checks before launch
&lt;/h2&gt;

&lt;p&gt;Review these items before you ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostHog is registered only through c15t, not through &lt;code&gt;next/script&lt;/code&gt; or a tag manager that bypasses c15t.&lt;/li&gt;
&lt;li&gt;The helper uses the correct &lt;code&gt;region&lt;/code&gt; for your PostHog project.&lt;/li&gt;
&lt;li&gt;The chosen &lt;code&gt;loadMode&lt;/code&gt; matches your privacy notice and legal review.&lt;/li&gt;
&lt;li&gt;Event calls are guarded when you use &lt;code&gt;loadMode: 'after-consent'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Users have a persistent way to reopen privacy preferences and withdraw measurement consent.&lt;/li&gt;
&lt;li&gt;PostHog settings for IP handling, person profiles, session replay, retention, and deletion match your policy.&lt;/li&gt;
&lt;li&gt;Your c15t backend records consent and preference changes as required for your setup.&lt;/li&gt;
&lt;li&gt;EU/EEA wording has legal review, especially if you make country-specific claims.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://c15t.com/docs/frameworks/next/quickstart" rel="noopener noreferrer"&gt;c15t Next.js quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://c15t.com/docs/frameworks/next/script-loader" rel="noopener noreferrer"&gt;c15t Next.js script loader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://c15t.com/docs/frameworks/next/concepts/cookie-management" rel="noopener noreferrer"&gt;c15t cookie management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://c15t.com/docs/integrations/posthog" rel="noopener noreferrer"&gt;c15t PostHog integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://posthog.com/docs/privacy/data-storage" rel="noopener noreferrer"&gt;PostHog data storage and privacy controls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://posthog.com/docs/privacy/gdpr-compliance" rel="noopener noreferrer"&gt;PostHog GDPR guidance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02002L0058-20091219" rel="noopener noreferrer"&gt;EU ePrivacy Directive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:02016R0679-20160504" rel="noopener noreferrer"&gt;GDPR text&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.edpb.europa.eu/our-work-tools/our-documents/guidelines/guidelines-052020-consent-under-regulation-2016679_en" rel="noopener noreferrer"&gt;EDPB Guidelines 05/2020 on consent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.edpb.europa.eu/documents/guideline/guidelines-22023-on-technical-scope-of-art-53-of-eprivacy-directive_en" rel="noopener noreferrer"&gt;EDPB Guidelines 2/2023 on the technical scope of ePrivacy Article 5(3)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;You now have a Next.js consent setup where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;c15t owns the PostHog script lifecycle.&lt;/li&gt;
&lt;li&gt;PostHog loads only after &lt;code&gt;measurement&lt;/code&gt; consent in the strict setup.&lt;/li&gt;
&lt;li&gt;Client event calls are guarded before PostHog exists.&lt;/li&gt;
&lt;li&gt;Consent withdrawal reloads the page by default so revoked scripts do not run in the fresh context.&lt;/li&gt;
&lt;li&gt;EU privacy claims are scoped to implementation support, not compliance guarantees.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your application code does not need to decide when PostHog should load. c15t manages that lifecycle, and your app captures events only after analytics is available.&lt;/p&gt;

&lt;p&gt;Next, read the &lt;a href="https://c15t.com/docs/integrations/posthog" rel="noopener noreferrer"&gt;c15t PostHog integration docs&lt;/a&gt; for the full option reference. If you need server awareness, read the &lt;a href="https://c15t.com/docs/frameworks/next/server-side" rel="noopener noreferrer"&gt;c15t Next.js server-side data guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>tutorial</category>
      <category>posthog</category>
      <category>gdpr</category>
    </item>
  </channel>
</rss>
