@consentx/next
v1.0.0
Published
ConsentX cookie-consent & CMP for Next.js — GDPR, CCPA, DPDPA. Drop in <ConsentX siteKey /> to load the self-installing consent banner. Works with the App Router and the Pages Router.
Maintainers
Readme
@consentx/next
Cookie consent & CMP for Next.js — GDPR, CCPA, DPDPA. Drop one component into your app and the self-installing ConsentX banner does the rest: geo-aware notice, pre-consent tracker blocking, Google Consent Mode v2, and a consent receipt — all driven from your ConsentX dashboard.
- Works with the App Router and the Pages Router.
- Loads the embed with
next/scriptstrategy="afterInteractive", so React hydration never strips the widget's mount div. - Typed props, a
useConsent()hook, ESM + CJS, zero runtime dependencies.
Install
npm install @consentx/next
# or
pnpm add @consentx/next
# or
yarn add @consentx/nextreact and next are peer dependencies (Next 13+, React 18+).
Get your Site Key
This package uses the site-key model (no server handshake):
- In the ConsentX dashboard go to Websites → your site and copy the Site Key.
- Make sure your domain is on that site's allowlist (the dashboard "Add website" flow handles this).
- Paste the key into
<ConsentX siteKey="…" />below.
Tip: keep the key in an env var, e.g.
NEXT_PUBLIC_CONSENTX_SITE_KEY. It is a public site identifier (it ships to the browser), not a secret.
Usage — App Router (app/layout.tsx)
Render <ConsentX /> once, inside <body>, in your root layout:
// app/layout.tsx
import { ConsentX } from "@consentx/next";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<ConsentX siteKey={process.env.NEXT_PUBLIC_CONSENTX_SITE_KEY!} />
</body>
</html>
);
}Usage — Pages Router (pages/_app.tsx)
// pages/_app.tsx
import type { AppProps } from "next/app";
import { ConsentX } from "@consentx/next";
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<ConsentX siteKey={process.env.NEXT_PUBLIC_CONSENTX_SITE_KEY!} />
</>
);
}That's it. The component sets window.consentx_key, injects
https://app.consentx.io/api/<siteKey>/embed.js as an ES module, and the widget
renders into a #consentx-cookie-consent div it appends to <body>.
Reading consent in your app
Use the useConsent() hook to react to the visitor's choice (it listens to the
widget's cx:consent event):
"use client";
import { useConsent } from "@consentx/next";
export function Analytics() {
const { hasConsent, decided } = useConsent();
// Only load analytics once the visitor has granted the "analytics" category.
if (!decided || !hasConsent("analytics")) return null;
return <script src="https://www.googletagmanager.com/gtag/js?id=G-XXXX" async />;
}Open the preferences panel from anywhere (e.g. a footer "Cookie settings" link):
const { openPreferences } = useConsent();
<button onClick={openPreferences}>Cookie settings</button>;Google Consent Mode v2
Set consentMode to seed denied-by-default signals before any analytics tag
runs. The ConsentX widget emits the matching gtag('consent','update',…) calls
on the visitor's choice:
<ConsentX siteKey={key} consentMode />Props
| Prop | Type | Default | Description |
| ------------- | ------------------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------- |
| siteKey | string (required) | — | Your ConsentX Site Key (dashboard → Websites → your site). |
| appHost | string | https://app.consentx.io | App host serving embed.js. Override for staging / self-hosted. |
| strategy | "afterInteractive" \| "lazyOnload" \| "beforeInteractive" | "afterInteractive" | next/script load strategy. Keep afterInteractive unless you know the SPA hydration caveat. |
| consentMode | boolean | false | Emit Google Consent Mode v2 denied-by-default signals. |
| nonce | string | — | CSP nonce forwarded to the injected scripts. |
| onLoad | () => void | — | Fired once embed.js loads. |
| onError | (error: Error) => void | — | Fired if embed.js fails to load (bad key, network, allowlist). |
Hook: useConsent()
| Field | Type | Description |
| ----------------- | ------------------------------- | --------------------------------------------------------------------- |
| granted | string[] \| null | Granted category slugs from the latest decision; null before first. |
| decided | boolean | true once a cx:consent event has fired. |
| hasConsent | (category: string) => boolean | Whether a specific category is granted. |
| openPreferences | () => void | Re-open the ConsentX banner / preferences. |
Staging / self-hosted host
<ConsentX siteKey={key} appHost="https://consentx1.satyamrastogi.com" />How it works
ConsentX serves a single self-contained module, embed.js, per Site Key. This
package's only job is clean embed injection: set window.consentx_key, then
load <appHost>/api/<siteKey>/embed.js as a type="module" script after
hydration. embed.js injects the scoped widget CSS + JS, reads geo/config from
the server, renders the banner into #consentx-cookie-consent, exposes
window.ConsentX, and emits the cx:consent event — no redirect handshake.
Build from source
npm install
npm run build # tsup → dist/ (ESM + CJS + d.ts)
npm run typecheckLicense
MIT © ConsentX · consentx.io
