Next.js (App Router)
1. Client provider
Section titled “1. Client provider”The provider and all components are client components. Put them in their own file:
"use client";
import { ConsentBanner, type ConsentState, PermitoProvider, PreferenceCenter, PreferencesButton,} from "@permito/react";import type { ReactNode } from "react";
export function Providers({ initialState, children,}: { initialState: ConsentState | null; children: ReactNode;}) { return ( <PermitoProvider config={{ consentVersion: "2026-10", language: "de-CH", initialState, categories: [ { id: "necessary", required: true }, { id: "statistics" }, { id: "marketing" }, ], services: [ { id: "plausible", name: "Plausible", category: "statistics" }, { id: "youtube", name: "YouTube", provider: "Google Ireland Ltd.", category: "marketing" }, ], }} privacyPolicyUrl="/datenschutz" googleConsentMode > {children} <ConsentBanner /> <PreferenceCenter /> <PreferencesButton /> </PermitoProvider> );}2. Root layout
Section titled “2. Root layout”Read the consent cookie on the server and pass it down. Import server helpers from @permito/react/server, which has no "use client" directive.
import "@permito/react/styles.css";import { getConsentModeDefaultScript, readConsentFromCookieHeader } from "@permito/react/server";import { headers } from "next/headers";import type { ReactNode } from "react";import { Providers } from "./providers";
export default async function RootLayout({ children }: { children: ReactNode }) { const initialState = readConsentFromCookieHeader((await headers()).get("cookie")); return ( <html lang="de-CH"> <head> <script dangerouslySetInnerHTML={{ __html: getConsentModeDefaultScript() }} /> </head> <body> <Providers initialState={initialState}>{children}</Providers> </body> </html> );}Reading the cookie makes the route dynamic. If you prefer static pages, leave out initialState: gated content then appears right after hydration instead.
3. Gate content
Section titled “3. Gate content”import { ConsentGate, ConsentIframe, ConsentScript } from "@permito/react";
export default function Page() { return ( <> <ConsentScript service="plausible" src="https://plausible.io/js/script.js" defer attributes={{ "data-domain": "example.com" }} /> <ConsentGate category="statistics" fallback={<p>Statistics are off.</p>}> <p>Statistics are on.</p> </ConsentGate> <ConsentIframe service="youtube" title="Product video" src="https://www.youtube-nocookie.com/embed/VIDEO_ID" width={560} height={315} /> </> );}Components from @permito/react can be used directly in server components; they are client components under the hood.
Content Security Policy
Section titled “Content Security Policy”If you use a nonce-based CSP, pass the nonce to the inline Consent Mode script and to ConsentScript (nonce prop) or to blockedElements={{ nonce }}.
A complete, tested example lives in examples/nextjs.