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/OneConsentCategoryNameunions — theserviceandcategoryprops 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:
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.
Using consent in components
Access consent state in client components with the useConsent hook:
'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
| Prop | Type | Default | Description |
|---|---|---|---|
appId | string | NEXT_PUBLIC_1CONSENT_APP_ID | Your 1Consent App ID |
fastPathVersions | Readonly<Record<string, string>> | — | Framework-ID → build-stamp map from 1consent.gen.ts; enables the parse-time fast path |
nonce | string | — | CSP nonce for the inline engine script |
cmpSrc | string | derived from appId | Override the CMP embed script URL |
environment | local | develop | production | production | Select a 1Consent service environment (internal setups only) |
loadCmp | boolean | true | Set 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).
Consent semantics
- 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
structuralVersionand 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--checkto CI. - Use environment variables for the App ID to keep it configurable per deployment.
- Wrap, don't fork: prefer
<OneConsent>over conditional rendering withuseConsent— wrapped content gets the parse-time fast path; hook-gated content always waits for hydration.