1ConsentConsentDocs

React Integration

Add consent management with the @1consent/react package. It is pure React — it works in Next.js (App Router and Pages Router), Vite SPAs, Remix/React Router, and Astro islands.

How it works: wrap any third-party markup in <OneConsent>. It renders as an inert <template> that cannot execute — no scripts run, no images load, no iframes connect. A tiny inline engine (installed by the provider) resolves it:

  • Returning visitors with stored consent get their scripts at HTML parse time, before first paint — the same timing as a direct embed, and it works even when ad blockers block the CMP script.
  • First-time visitors see the consent banner; gated content is released the moment they decide.
  • The server never reads consent. Your HTML is byte-identical for every visitor, so static rendering, ISR, and CDN caching are fully preserved.

Installation

@1consent/react, @1consent/nextjs, and the 1c CLI are currently developer-preview workspace packages. Public npm installation is not available yet; package installation commands will be added only after the provenance-backed release.

Setup

Generate typed service names

The public type-generation command is intentionally withheld during the developer preview. The source-backed output contract is documented below; the verified scoped CLI invocation will be added only after the provenance-backed npm release.

This writes src/1consent.gen.ts — or 1consent.gen.ts at the project root if you have no src/ directory. Point --out anywhere you like to override it. The file contains:

  • OneConsentServiceName / OneConsentCategoryName unions — the service and category props autocomplete, and renaming a service in the dashboard becomes a build-time type error pointing at the exact JSX usage,
  • fastPathVersions — a framework-ID → structural-version map, so the inline engine can trust the exact framework selected for each visitor. Service and category types are unioned across every published framework.

Re-run after changing services or categories. The released CLI's --check mode will let CI catch stale types without rewriting the file.

Add the Provider

Wrap your application in your root layout:

app/layout.tsx
import { OneConsentProvider } from '@1consent/react';
import { fastPathVersions } from '@/1consent.gen';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <OneConsentProvider
          appId="YOUR_APP_ID"
          fastPathVersions={fastPathVersions}
        >
          {children}
        </OneConsentProvider>
      </body>
    </html>
  );
}

The provider renders the inline consent engine and loads the CMP (banner + preference center). appId falls back to the NEXT_PUBLIC_1CONSENT_APP_ID environment variable.

@/1consent.gen is your project's own path alias — in Next.js and Vite, @/ resolves to src/, so it matches the file 1c typegen writes to src/1consent.gen.ts. If your project has no @/ alias (or no src/ directory), import the generated file with a relative path instead.

Omitting fastPathVersions is safe — everything still works, but returning visitors lose the parse-time fast path until the CMP loads. The synchronous hosted config selects the visitor's live framework and consent epoch; the generated map only supplies that framework's structural build stamp. A publish that resurfaces consent therefore takes effect without a customer rebuild.

Wrap third-party scripts

Wrap anything that requires consent — scripts, pixels, iframes, embeds:

import { OneConsent } from '@1consent/react';

<OneConsent service="Google Analytics" once>
  <script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX" />
  <script>{`window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXX');`}</script>
</OneConsent>

Or gate by category:

<OneConsent category="Marketing">
  <iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" />
</OneConsent>

The service value matches the service's name, tag, or id as configured in your dashboard. Children must be plain HTML elements (they are serialized into inert HTML — React components can't be gated this way; gate the component's own output instead).

Remount semantics — when to use once: if a <OneConsent> unmounts and remounts (SPA navigation, conditional render), its content is promoted again. That is what you want for visual content and UI-rendering scripts — the embed was destroyed with the old mount and must be restored. It is not what you want for global side-effect scripts like the gtag loader above, which would execute twice. Add once to those: the block then runs at most once per page load, no matter how often it remounts. Never put once on visual embeds.

<OneConsent> works from anywhere in the document, but note that its wrapper is a <div>, which is not valid inside <head>: browsers silently move it — and any tags written after it in <head> — into <body>, and React logs a hydration mismatch in development. Gated content is promoted at parse time either way, so placing trackers at the top of <body> loads them just as early while keeping your markup valid.

Access consent state in client components with the useConsent hook:

components/personalized-banner.tsx
'use client';
import { useConsent } from '@1consent/react';

export function PersonalizedBanner() {
  const granted = useConsent('Google Analytics'); // service or category

  if (!granted) return null;

  return <TrackedContent />;
}

The hook returns false until consent is known and re-renders on consent changes.

Provider configuration

PropTypeDefaultDescription
appIdstringNEXT_PUBLIC_1CONSENT_APP_IDYour 1Consent App ID
fastPathVersionsReadonly<Record<string, string>>Framework-ID → build-stamp map from 1consent.gen.ts; enables the parse-time fast path
noncestringCSP nonce for the inline engine script
cmpSrcstringderived from appIdOverride the CMP embed script URL
environmentlocal | develop | productionproductionSelect a 1Consent service environment (internal setups only)
loadCmpbooleantrueSet false when the CMP embed is added elsewhere (e.g. GTM)

<OneConsent> props: exactly one of service / category, plus optional nonce (strict-CSP sites) and once (promote at most once per page load — for side-effect scripts that must not re-execute on remount; identical once blocks share the guard).

  • Gated content only ever transitions inert → live. When a visitor revokes previously granted consent, the CMP reloads the page — scripts cannot be "un-run".
  • If the consent configuration changes structurally (services added/removed), stored consent no longer matches structuralVersion and the engine defers to the CMP — visitors are never given scripts they didn't consent to.
  • Crawlers and bots (no stored consent) never see gated scripts execute.

Best practices

  • Commit 1consent.gen.ts (or regenerate it in your build step) and add --check to CI.
  • Use environment variables for the App ID to keep it configurable per deployment.
  • Wrap, don't fork: prefer <OneConsent> over conditional rendering with useConsent — wrapped content gets the parse-time fast path; hook-gated content always waits for hydration.

On this page