@c15t/nextjs 2.0.0-rc.0 → 2.0.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/client/components/consent-dialog-link.js +3 -0
- package/dist/headless.cjs +1 -1
- package/dist/iab/styles.css +12 -0
- package/dist/iab/styles.tw3.css +14 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/libs/browser-initial-data.cjs +1 -0
- package/dist/libs/browser-initial-data.js +1 -0
- package/dist/libs/initial-data.cjs +1 -1
- package/dist/libs/initial-data.js +1 -1
- package/dist/styles.css +10 -0
- package/dist/styles.tw3.css +13 -0
- package/dist/types.cjs +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/{dist → dist-types}/headless.d.ts +0 -1
- package/{dist → dist-types}/index.d.ts +3 -2
- package/dist-types/libs/browser-initial-data.d.ts +9 -0
- package/{dist → dist-types}/libs/initial-data.d.ts +7 -2
- package/dist-types/types.d.ts +38 -0
- package/dist-types/version.d.ts +1 -0
- package/docs/README.md +73 -0
- package/docs/building-headless-components.md +377 -0
- package/docs/callbacks.md +184 -0
- package/docs/components/consent-banner.md +269 -0
- package/docs/components/consent-dialog-link.md +59 -0
- package/docs/components/consent-dialog-trigger.md +103 -0
- package/docs/components/consent-dialog.md +177 -0
- package/docs/components/consent-manager-provider.md +425 -0
- package/docs/components/consent-widget.md +133 -0
- package/docs/components/dev-tools.md +63 -0
- package/docs/components/frame.md +73 -0
- package/docs/concepts/client-modes.md +175 -0
- package/docs/concepts/consent-categories.md +97 -0
- package/docs/concepts/consent-models.md +116 -0
- package/docs/concepts/cookie-management.md +122 -0
- package/docs/concepts/glossary.md +23 -0
- package/docs/concepts/initialization-flow.md +148 -0
- package/docs/concepts/policy-packs.md +229 -0
- package/docs/headless.md +190 -0
- package/docs/hooks/use-color-scheme.md +40 -0
- package/docs/hooks/use-consent-manager/checking-consent.md +94 -0
- package/docs/hooks/use-consent-manager/location-info.md +95 -0
- package/docs/hooks/use-consent-manager/overview.md +420 -0
- package/docs/hooks/use-consent-manager/setting-consent.md +92 -0
- package/docs/hooks/use-draggable.md +57 -0
- package/docs/hooks/use-focus-trap.md +41 -0
- package/docs/hooks/use-reduced-motion.md +35 -0
- package/docs/hooks/use-ssr-status.md +31 -0
- package/docs/hooks/use-text-direction.md +49 -0
- package/docs/hooks/use-translations.md +118 -0
- package/docs/iab/consent-banner.md +94 -0
- package/docs/iab/consent-dialog.md +134 -0
- package/docs/iab/overview.md +126 -0
- package/docs/iab/use-gvl-data.md +20 -0
- package/docs/iframe-blocking.md +107 -0
- package/docs/integrations/building-integrations.md +405 -0
- package/docs/integrations/databuddy.md +203 -0
- package/docs/integrations/google-tag-manager.md +153 -0
- package/docs/integrations/google-tag.md +122 -0
- package/docs/integrations/linkedin-insights.md +109 -0
- package/docs/integrations/meta-pixel.md +342 -0
- package/docs/integrations/microsoft-uet.md +112 -0
- package/docs/integrations/overview.md +105 -0
- package/docs/integrations/posthog.md +199 -0
- package/docs/integrations/tiktok-pixel.md +113 -0
- package/docs/integrations/x-pixel.md +143 -0
- package/docs/internationalization.md +197 -0
- package/docs/network-blocker.md +178 -0
- package/docs/optimization.md +234 -0
- package/docs/policy-packs.md +246 -0
- package/docs/quickstart.md +161 -0
- package/docs/script-loader.md +321 -0
- package/docs/server-side.md +176 -0
- package/docs/styling/classnames.md +92 -0
- package/docs/styling/color-scheme.md +82 -0
- package/docs/styling/css-variables.md +92 -0
- package/docs/styling/overview.md +456 -0
- package/docs/styling/slots.md +127 -0
- package/docs/styling/tailwind.md +113 -0
- package/docs/styling/tokens.md +216 -0
- package/docs/troubleshooting.md +146 -0
- package/iab/styles.css +1 -0
- package/package.json +38 -17
- package/readme.json +4 -0
- package/src/iab/styles.css +12 -0
- package/src/iab/styles.tw3.css +14 -0
- package/src/styles.css +10 -0
- package/src/styles.tw3.css +13 -0
- package/styles.css +1 -0
- package/dist/headless.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/libs/initial-data.d.ts.map +0 -1
- package/dist/types.d.ts +0 -16
- package/dist/types.d.ts.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Quickstart
|
|
3
|
+
description: Add consent management to your Next.js app in under 5 minutes.
|
|
4
|
+
lastModified: 2026-03-11
|
|
5
|
+
availableIn:
|
|
6
|
+
- framework: 'javascript'
|
|
7
|
+
url: '/docs/frameworks/javascript/quickstart'
|
|
8
|
+
title: 'JavaScript'
|
|
9
|
+
- framework: 'react'
|
|
10
|
+
url: '/docs/frameworks/react/quickstart'
|
|
11
|
+
title: 'React'
|
|
12
|
+
- framework: 'next'
|
|
13
|
+
url: '/docs/frameworks/next/quickstart'
|
|
14
|
+
title: 'Next.js'
|
|
15
|
+
---
|
|
16
|
+
## Via CLI
|
|
17
|
+
|
|
18
|
+
|Package manager|Command|
|
|
19
|
+
|:--|:--|
|
|
20
|
+
|npm|`npx @c15t/cli@rc`|
|
|
21
|
+
|pnpm|`pnpm dlx @c15t/cli@rc`|
|
|
22
|
+
|yarn|`yarn dlx @c15t/cli@rc`|
|
|
23
|
+
|bun|`bunx @c15t/cli@rc`|
|
|
24
|
+
|
|
25
|
+
## Manual Installation
|
|
26
|
+
|
|
27
|
+
1. **Install package**
|
|
28
|
+
|
|
29
|
+
|Package manager|Command|
|
|
30
|
+
|:--|:--|
|
|
31
|
+
|npm|`npm install @c15t/nextjs`|
|
|
32
|
+
|pnpm|`pnpm add @c15t/nextjs`|
|
|
33
|
+
|yarn|`yarn add @c15t/nextjs`|
|
|
34
|
+
|bun|`bun add @c15t/nextjs`|
|
|
35
|
+
|
|
36
|
+
2. **Import styles** Import the prebuilt component stylesheet in your app-level CSS entrypoint. This is required for styled components to render correctly.
|
|
37
|
+
|
|
38
|
+
```css
|
|
39
|
+
@import "@c15t/nextjs/styles.css";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Keeping the c15t stylesheet in your global CSS entrypoint makes layer and cascade order explicit. JS/TSX side-effect imports can load in a different order across framework and Tailwind tooling, which makes style regressions harder to debug.
|
|
43
|
+
|
|
44
|
+
> ℹ️ Info:
|
|
45
|
+
>
|
|
46
|
+
> If you are using the headless API or fully custom styling, you can skip this import. Your root layout should continue importing ./globals.css as usual.
|
|
47
|
+
|
|
48
|
+
3. **Create ConsentManager components** Create a provider component with the consent UI and a wrapper that re-exports it. This initializes the consent store and makes consent state available to all child components.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
'use client';
|
|
52
|
+
|
|
53
|
+
import { type ReactNode } from 'react';
|
|
54
|
+
import {
|
|
55
|
+
ConsentManagerProvider,
|
|
56
|
+
ConsentBanner,
|
|
57
|
+
ConsentDialog,
|
|
58
|
+
} from '@c15t/nextjs';
|
|
59
|
+
|
|
60
|
+
export default function ConsentManagerClient({ children }: { children: ReactNode }) {
|
|
61
|
+
return (
|
|
62
|
+
<ConsentManagerProvider
|
|
63
|
+
options={{
|
|
64
|
+
mode: 'hosted',
|
|
65
|
+
backendURL: 'https://your-instance.c15t.dev',
|
|
66
|
+
consentCategories: ['necessary', 'measurement', 'marketing'],
|
|
67
|
+
// Shows banner during development. Remove for production.
|
|
68
|
+
overrides: { country: 'DE' },
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<ConsentBanner />
|
|
72
|
+
<ConsentDialog />
|
|
73
|
+
{children}
|
|
74
|
+
</ConsentManagerProvider>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import type { ReactNode } from 'react';
|
|
81
|
+
import ConsentManagerProvider from './provider';
|
|
82
|
+
|
|
83
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
84
|
+
return <ConsentManagerProvider>{children}</ConsentManagerProvider>;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> ℹ️ Info:
|
|
89
|
+
>
|
|
90
|
+
> Hosted mode is the recommended production setup because the backend resolves jurisdiction and policy, keeps durable consent records, and lets c15t recover from temporary network failures by re-syncing later.
|
|
91
|
+
>
|
|
92
|
+
> ℹ️ Info:
|
|
93
|
+
>
|
|
94
|
+
> Don't have a backend yet? You can use mode: 'offline' for local-only consent storage, but it gives up backend audit history, server-side consent awareness, and automatic jurisdiction detection. Review the browser-only storage consequences before choosing it for production.
|
|
95
|
+
|
|
96
|
+
4. **Mount ConsentManager at the app root** Wrap your app tree with ConsentManager so all routes/components can access consent state.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { ConsentManager } from '@/components/consent-manager';
|
|
100
|
+
|
|
101
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
102
|
+
return (
|
|
103
|
+
<html lang="en">
|
|
104
|
+
<body>
|
|
105
|
+
<ConsentManager>{children}</ConsentManager>
|
|
106
|
+
</body>
|
|
107
|
+
</html>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
5. **Verify it works** Start your development server and confirm:
|
|
113
|
+
|
|
114
|
+
A consent banner appears at the bottom of the pageClicking "Customize" opens a dialog with toggles for each consent categoryAfter accepting or rejecting, the banner dismisses and your choice persists across page reloads
|
|
115
|
+
|
|
116
|
+
> ℹ️ **Info:**
|
|
117
|
+
> Want better performance and static-route support? See Optimization for rewrites, prefetching, and network tuning. If you want server-side data prefetching, see Server-Side Data Fetching.
|
|
118
|
+
|
|
119
|
+
## Optional: Add DevTools
|
|
120
|
+
|
|
121
|
+
Install DevTools only if you want a runtime inspector while building and debugging:
|
|
122
|
+
|
|
123
|
+
|Package manager|Command|
|
|
124
|
+
|:--|:--|
|
|
125
|
+
|npm|`npm install @c15t/dev-tools`|
|
|
126
|
+
|pnpm|`pnpm add @c15t/dev-tools`|
|
|
127
|
+
|yarn|`yarn add @c15t/dev-tools`|
|
|
128
|
+
|bun|`bun add @c15t/dev-tools`|
|
|
129
|
+
|
|
130
|
+
Then add it inside your existing provider:
|
|
131
|
+
|
|
132
|
+
```tsx title="components/consent-manager/provider.tsx"
|
|
133
|
+
import { DevTools } from '@c15t/dev-tools/react';
|
|
134
|
+
|
|
135
|
+
// ...
|
|
136
|
+
|
|
137
|
+
<ConsentManagerProvider options={...}>
|
|
138
|
+
<ConsentBanner />
|
|
139
|
+
<ConsentDialog />
|
|
140
|
+
{process.env.NODE_ENV !== 'production' && <DevTools />}
|
|
141
|
+
{children}
|
|
142
|
+
</ConsentManagerProvider>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> ℹ️ **Info:**
|
|
146
|
+
> Want to understand what's happening under the hood? See Initialization Flow for the lifecycle and Cookie Management for script/cookie behavior and revocation handling.
|
|
147
|
+
|
|
148
|
+
## Optional: AI Agents
|
|
149
|
+
|
|
150
|
+
Install c15t agent skills to let AI agents help with styling, i18n, scripts & other configuration.
|
|
151
|
+
|
|
152
|
+
|Package manager|Command|
|
|
153
|
+
|:--|:--|
|
|
154
|
+
|npm|`npx @c15t/cli@rc skills`|
|
|
155
|
+
|pnpm|`pnpm dlx @c15t/cli@rc skills`|
|
|
156
|
+
|yarn|`yarn dlx @c15t/cli@rc skills`|
|
|
157
|
+
|bun|`bunx @c15t/cli@rc skills`|
|
|
158
|
+
|
|
159
|
+
See [AI Agents](/docs/ai-agents) for bundled package docs and agent skills.
|
|
160
|
+
|
|
161
|
+
## Next steps
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Script Loader
|
|
3
|
+
description: Gate third-party scripts behind consent - load Google Analytics, Meta Pixel, and other tracking scripts only when users grant permission.
|
|
4
|
+
---
|
|
5
|
+
The script loader manages third-party scripts based on consent state. Scripts are defined in the provider's `scripts` option and are automatically loaded when their required consent category is granted, and unloaded when consent is revoked.
|
|
6
|
+
|
|
7
|
+
c15t has a collection of premade scripts available in `@c15t/scripts`. Check the [integrations overview](/docs/integrations/overview) first before manually building a script.
|
|
8
|
+
|
|
9
|
+
|Package manager|Command|
|
|
10
|
+
|:--|:--|
|
|
11
|
+
|npm|`npm install @c15t/scripts`|
|
|
12
|
+
|pnpm|`pnpm add @c15t/scripts`|
|
|
13
|
+
|yarn|`yarn add @c15t/scripts`|
|
|
14
|
+
|bun|`bun add @c15t/scripts`|
|
|
15
|
+
|
|
16
|
+
> ℹ️ **Info:**
|
|
17
|
+
> We recommend using the pre-built integrations when possible.
|
|
18
|
+
>
|
|
19
|
+
> ℹ️ **Info:**
|
|
20
|
+
> If you need a vendor we do not ship yet, see the custom integration guide. It covers both one-off Script objects and reusable manifest-backed integrations.
|
|
21
|
+
>
|
|
22
|
+
> ℹ️ **Info:**
|
|
23
|
+
> For app-specific scripts, use a plain Script object. For reusable integrations, prefer a manifest-backed helper so startup phases, consent signaling, and future server-side loading support stay structured.
|
|
24
|
+
|
|
25
|
+
## Basic Usage
|
|
26
|
+
|
|
27
|
+
Pass an array of `Script` objects to the provider:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { type ReactNode } from 'react';
|
|
31
|
+
import { ConsentManagerProvider } from '@c15t/nextjs';
|
|
32
|
+
import { metaPixel } from '@c15t/scripts/meta-pixel';
|
|
33
|
+
|
|
34
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
35
|
+
return (
|
|
36
|
+
<ConsentManagerProvider
|
|
37
|
+
options={{
|
|
38
|
+
mode: 'hosted',
|
|
39
|
+
backendURL: '/api/c15t',
|
|
40
|
+
scripts: [
|
|
41
|
+
metaPixel({ pixelId: '123456' }),
|
|
42
|
+
{
|
|
43
|
+
id: 'custom-analytics',
|
|
44
|
+
src: 'https://cdn.example.com/analytics.js',
|
|
45
|
+
category: 'measurement',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</ConsentManagerProvider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Choose the Right Approach
|
|
57
|
+
|
|
58
|
+
* Use a plain `Script` for one-off app code.
|
|
59
|
+
* Use a manifest-backed helper in `@c15t/scripts` for reusable integrations, contributions, or anything that needs structured startup behavior.
|
|
60
|
+
|
|
61
|
+
If you are building something reusable, start with the [custom integration guide](/docs/integrations/building-integrations) before using raw callbacks.
|
|
62
|
+
|
|
63
|
+
## Reusable Integrations
|
|
64
|
+
|
|
65
|
+
For app-specific use, raw `Script` objects are usually enough.
|
|
66
|
+
|
|
67
|
+
For reusable integrations, c15t uses a manifest-backed model in `@c15t/scripts`. That keeps startup phases, consent signaling, and vendor-specific boot logic structured instead of hidden inside large callback bodies.
|
|
68
|
+
|
|
69
|
+
If you are building an integration for multiple apps or contributing upstream, use the [custom integration guide](/docs/integrations/building-integrations).
|
|
70
|
+
|
|
71
|
+
## Script Types
|
|
72
|
+
|
|
73
|
+
### Standard Scripts
|
|
74
|
+
|
|
75
|
+
Load an external JavaScript file via a `<script>` tag. Use `src` to specify the URL.
|
|
76
|
+
|
|
77
|
+
### Inline Scripts
|
|
78
|
+
|
|
79
|
+
Execute inline JavaScript code. Use `textContent` instead of `src`:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
{
|
|
83
|
+
id: 'gtag-config',
|
|
84
|
+
textContent: `
|
|
85
|
+
window.dataLayer = window.dataLayer || [];
|
|
86
|
+
function gtag(){dataLayer.push(arguments);}
|
|
87
|
+
gtag('js', new Date());
|
|
88
|
+
gtag('config', 'G-XXXXXX');
|
|
89
|
+
`,
|
|
90
|
+
category: 'measurement',
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Callback-Only Scripts
|
|
95
|
+
|
|
96
|
+
Don't inject any `<script>` tag - just execute callbacks based on consent changes. Useful for controlling libraries that are already loaded:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
{
|
|
100
|
+
id: 'posthog-consent',
|
|
101
|
+
callbackOnly: true,
|
|
102
|
+
category: 'measurement',
|
|
103
|
+
onLoad: ({ hasConsent }) => {
|
|
104
|
+
if (hasConsent) {
|
|
105
|
+
posthog.opt_in_capturing();
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
onConsentChange: ({ hasConsent }) => {
|
|
109
|
+
if (hasConsent) {
|
|
110
|
+
posthog.opt_in_capturing();
|
|
111
|
+
} else {
|
|
112
|
+
posthog.opt_out_capturing();
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Consent Conditions
|
|
119
|
+
|
|
120
|
+
The `category` field accepts a `HasCondition` - either a simple string or a logical expression:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// Simple: requires measurement consent
|
|
124
|
+
{ category: 'measurement' }
|
|
125
|
+
|
|
126
|
+
// AND: requires both measurement and marketing
|
|
127
|
+
{ category: { and: ['measurement', 'marketing'] } }
|
|
128
|
+
|
|
129
|
+
// OR: requires either measurement or marketing
|
|
130
|
+
{ category: { or: ['measurement', 'marketing'] } }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Script Callbacks
|
|
134
|
+
|
|
135
|
+
Every script supports four lifecycle callbacks:
|
|
136
|
+
|
|
137
|
+
|Callback|When|Use Case|
|
|
138
|
+
|--|--|--|
|
|
139
|
+
|`onBeforeLoad`|Before the script tag is injected|Set up global variables|
|
|
140
|
+
|`onLoad`|Script loaded successfully|Initialize the library|
|
|
141
|
+
|`onError`|Script failed to load|Log error, load fallback|
|
|
142
|
+
|`onConsentChange`|Consent state changed (script already loaded)|Toggle tracking on/off|
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
{
|
|
146
|
+
id: 'analytics',
|
|
147
|
+
src: 'https://analytics.example.com/v2.js',
|
|
148
|
+
category: 'measurement',
|
|
149
|
+
onBeforeLoad: ({ id }) => {
|
|
150
|
+
console.log(`Loading script: ${id}`);
|
|
151
|
+
},
|
|
152
|
+
onLoad: ({ element }) => {
|
|
153
|
+
window.analytics.init('my-key');
|
|
154
|
+
},
|
|
155
|
+
onError: ({ error }) => {
|
|
156
|
+
console.error('Failed to load analytics:', error);
|
|
157
|
+
},
|
|
158
|
+
onConsentChange: ({ hasConsent, consents }) => {
|
|
159
|
+
window.analytics.setConsent(hasConsent);
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Advanced Options
|
|
165
|
+
|
|
166
|
+
### Always Load
|
|
167
|
+
|
|
168
|
+
Scripts that manage their own consent internally (like GTM in consent mode):
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
{
|
|
172
|
+
id: 'google-tag-manager',
|
|
173
|
+
src: 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXX',
|
|
174
|
+
category: 'measurement',
|
|
175
|
+
alwaysLoad: true, // Loads regardless of consent state
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Persist After Revocation
|
|
180
|
+
|
|
181
|
+
Keep the script loaded even after consent is revoked (the page won't reload for this script):
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
{
|
|
185
|
+
id: 'error-tracking',
|
|
186
|
+
src: 'https://errors.example.com/track.js',
|
|
187
|
+
category: 'measurement',
|
|
188
|
+
persistAfterConsentRevoked: true,
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Script Placement
|
|
193
|
+
|
|
194
|
+
Control where in the DOM the script is injected:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
{
|
|
198
|
+
id: 'widget',
|
|
199
|
+
src: 'https://widget.example.com/embed.js',
|
|
200
|
+
category: 'experience',
|
|
201
|
+
target: 'body', // 'head' (default) or 'body'
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Ad Blocker Evasion
|
|
206
|
+
|
|
207
|
+
Script element IDs are anonymized by default to avoid ad blocker pattern matching:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
{
|
|
211
|
+
id: 'analytics',
|
|
212
|
+
src: '...',
|
|
213
|
+
category: 'measurement',
|
|
214
|
+
anonymizeId: true, // default: true
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Dynamic Script Management
|
|
219
|
+
|
|
220
|
+
Add, remove, or check scripts at runtime via `useConsentManager()`:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { useConsentManager } from '@c15t/nextjs';
|
|
224
|
+
|
|
225
|
+
function ScriptManager() {
|
|
226
|
+
const { setScripts, removeScript, isScriptLoaded, getLoadedScriptIds } = useConsentManager();
|
|
227
|
+
|
|
228
|
+
// Add scripts dynamically
|
|
229
|
+
setScripts([{ id: 'dynamic', src: '...', category: 'measurement' }]);
|
|
230
|
+
|
|
231
|
+
// Remove a script
|
|
232
|
+
removeScript('dynamic');
|
|
233
|
+
|
|
234
|
+
// Check if a script is loaded
|
|
235
|
+
const loaded = isScriptLoaded('google-analytics');
|
|
236
|
+
|
|
237
|
+
// Get all loaded script IDs
|
|
238
|
+
const allLoaded = getLoadedScriptIds();
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## API Reference
|
|
243
|
+
|
|
244
|
+
### Script
|
|
245
|
+
|
|
246
|
+
|Property|Type|Description|Default|Required|
|
|
247
|
+
|:--|:--|:--|:--|:--:|
|
|
248
|
+
|id|string|Unique identifier for the script|-|✅ Required|
|
|
249
|
+
|src|string \|undefined|URL of the script to load|-|Optional|
|
|
250
|
+
|textContent|string \|undefined|Inline JavaScript code to execute|-|Optional|
|
|
251
|
+
|category|HasCondition\<AllConsentNames>|Consent category or condition required to load this script|-|✅ Required|
|
|
252
|
+
|callbackOnly|boolean \|undefined|Whether this is a callback-only script that doesn't need to load an external resource. When true, no script tag will be added to the DOM, only callbacks will be executed.|false|Optional|
|
|
253
|
+
|persistAfterConsentRevoked|boolean \|undefined|Whether the script should persist after consent is revoked.|false|Optional|
|
|
254
|
+
|alwaysLoad|boolean \|undefined|Whether the script should always load regardless of consent state. This is useful for scripts like Google Tag Manager or PostHog that manage their own consent state internally. The script will load immediately and never be unloaded based on consent changes. Note: When using this option, you are responsible for ensuring the script itself respects user consent preferences through its own consent management.|false|Optional|
|
|
255
|
+
|fetchPriority|"high" \|"low" \|"auto" \|undefined|Priority hint for browser resource loading|-|Optional|
|
|
256
|
+
|attributes|Record\<string, string> \|undefined|Additional attributes to add to the script element|-|Optional|
|
|
257
|
+
|async|boolean \|undefined|Whether to use async loading|-|Optional|
|
|
258
|
+
|defer|boolean \|undefined|Whether to defer script loading|-|Optional|
|
|
259
|
+
|nonce|string \|undefined|Content Security Policy nonce|-|Optional|
|
|
260
|
+
|anonymizeId|boolean \|undefined|Whether to use an anonymized ID for the script element, this helps ensure the script is not blocked by ad blockers|true|Optional|
|
|
261
|
+
|target|"head" \|"body" \|undefined|Where to inject the script element in the DOM. Options: \`'head'\`: Scripts are appended to \`\<head>\` (default); \`'body'\`: Scripts are appended to \`\<body>\`|'head'|Optional|
|
|
262
|
+
|onBeforeLoad|Object \|undefined|Callback executed before the script is loaded|-|Optional|
|
|
263
|
+
|onLoad|Object \|undefined|Callback executed when the script loads successfully|-|Optional|
|
|
264
|
+
|onError|Object \|undefined|Callback executed if the script fails to load|-|Optional|
|
|
265
|
+
|onConsentChange|Object \|undefined|Callback executed whenever the consent store is changed. This callback only applies to scripts already loaded.|-|Optional|
|
|
266
|
+
|vendorId|string \|number \|undefined|IAB TCF vendor ID - links script to a registered vendor. When in IAB mode, the script will only load if this vendor has consent. Takes precedence over \`category\` when in IAB mode. Use custom vendor IDs (string or number) to gate non-IAB vendors too.|-|Optional|
|
|
267
|
+
|iabPurposes|number\[] \|undefined|IAB TCF purpose IDs this script requires consent for. When in IAB mode and no vendorId is set, the script will only load if ALL specified purposes have consent.|-|Optional|
|
|
268
|
+
|iabLegIntPurposes|number\[] \|undefined|IAB TCF legitimate interest purpose IDs. These purposes can operate under legitimate interest instead of consent. The script loads if all iabPurposes have consent OR all iabLegIntPurposes have legitimate interest established.|-|Optional|
|
|
269
|
+
|iabSpecialFeatures|number\[] \|undefined|IAB TCF special feature IDs this script requires. Options: 1: Use precise geolocation data; 2: Actively scan device characteristics for identification|-|Optional|
|
|
270
|
+
|
|
271
|
+
#### `onBeforeLoad`
|
|
272
|
+
|
|
273
|
+
Callback executed before the script is loaded
|
|
274
|
+
|
|
275
|
+
|Property|Type|Description|Default|Required|
|
|
276
|
+
|:--|:--|:--|:--|:--:|
|
|
277
|
+
|id|string|The original script ID|-|✅ Required|
|
|
278
|
+
|elementId|string|The actual DOM element ID used (anonymized if enabled)|-|✅ Required|
|
|
279
|
+
|hasConsent|boolean|Has consent|-|✅ Required|
|
|
280
|
+
|consents|ConsentState|The current consent state|-|✅ Required|
|
|
281
|
+
|element|HTMLScriptElement \|undefined|The script element (for load/error callbacks) Will be undefined for callback-only scripts|-|Optional|
|
|
282
|
+
|error|Error \|undefined|Error information (for error callbacks)|-|Optional|
|
|
283
|
+
|
|
284
|
+
#### `onLoad`
|
|
285
|
+
|
|
286
|
+
Callback executed when the script loads successfully
|
|
287
|
+
|
|
288
|
+
|Property|Type|Description|Default|Required|
|
|
289
|
+
|:--|:--|:--|:--|:--:|
|
|
290
|
+
|id|string|The original script ID|-|✅ Required|
|
|
291
|
+
|elementId|string|The actual DOM element ID used (anonymized if enabled)|-|✅ Required|
|
|
292
|
+
|hasConsent|boolean|Has consent|-|✅ Required|
|
|
293
|
+
|consents|ConsentState|The current consent state|-|✅ Required|
|
|
294
|
+
|element|HTMLScriptElement \|undefined|The script element (for load/error callbacks) Will be undefined for callback-only scripts|-|Optional|
|
|
295
|
+
|error|Error \|undefined|Error information (for error callbacks)|-|Optional|
|
|
296
|
+
|
|
297
|
+
#### `onError`
|
|
298
|
+
|
|
299
|
+
Callback executed if the script fails to load
|
|
300
|
+
|
|
301
|
+
|Property|Type|Description|Default|Required|
|
|
302
|
+
|:--|:--|:--|:--|:--:|
|
|
303
|
+
|id|string|The original script ID|-|✅ Required|
|
|
304
|
+
|elementId|string|The actual DOM element ID used (anonymized if enabled)|-|✅ Required|
|
|
305
|
+
|hasConsent|boolean|Has consent|-|✅ Required|
|
|
306
|
+
|consents|ConsentState|The current consent state|-|✅ Required|
|
|
307
|
+
|element|HTMLScriptElement \|undefined|The script element (for load/error callbacks) Will be undefined for callback-only scripts|-|Optional|
|
|
308
|
+
|error|Error \|undefined|Error information (for error callbacks)|-|Optional|
|
|
309
|
+
|
|
310
|
+
#### `onConsentChange`
|
|
311
|
+
|
|
312
|
+
Callback executed whenever the consent store is changed. This callback only applies to scripts already loaded.
|
|
313
|
+
|
|
314
|
+
|Property|Type|Description|Default|Required|
|
|
315
|
+
|:--|:--|:--|:--|:--:|
|
|
316
|
+
|id|string|The original script ID|-|✅ Required|
|
|
317
|
+
|elementId|string|The actual DOM element ID used (anonymized if enabled)|-|✅ Required|
|
|
318
|
+
|hasConsent|boolean|Has consent|-|✅ Required|
|
|
319
|
+
|consents|ConsentState|The current consent state|-|✅ Required|
|
|
320
|
+
|element|HTMLScriptElement \|undefined|The script element (for load/error callbacks) Will be undefined for callback-only scripts|-|Optional|
|
|
321
|
+
|error|Error \|undefined|Error information (for error callbacks)|-|Optional|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Server-Side Data Fetching
|
|
3
|
+
description: Pre-fetch consent data in Server Components with fetchInitialData for dynamic Next.js routes.
|
|
4
|
+
---
|
|
5
|
+
The `@c15t/nextjs` package provides `fetchInitialData`, a server-side function that fetches consent data before rendering. Use it when the route is already dynamic, or when you want the fastest first banner and are okay opting the route into dynamic rendering. If you need to keep a route fully static, use `C15tPrefetch` instead.
|
|
6
|
+
|
|
7
|
+
Because `fetchInitialData()` resolves request headers from `next/headers`, using it opts the route into dynamic rendering.
|
|
8
|
+
|
|
9
|
+
> ℹ️ **Info:**
|
|
10
|
+
> SSR hydration is part of the initialization flow. When SSR data is available, the client skips the API fetch entirely.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { fetchInitialData } from '@c15t/nextjs';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## fetchInitialData
|
|
17
|
+
|
|
18
|
+
The primary function for fetching consent data on the server. It calls the c15t backend's `/init` endpoint with the user's request headers (resolved automatically from `next/headers`) and returns the initial consent data.
|
|
19
|
+
|
|
20
|
+
```ts title="app/layout.tsx"
|
|
21
|
+
import { fetchInitialData } from '@c15t/nextjs';
|
|
22
|
+
|
|
23
|
+
// In a Server Component — do NOT await
|
|
24
|
+
const ssrData = fetchInitialData({
|
|
25
|
+
backendURL: 'https://your-instance.c15t.dev',
|
|
26
|
+
debug: process.env.NODE_ENV === 'development',
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> ℹ️ **Info:**
|
|
31
|
+
> Do not await fetchInitialData() in Server Components. Pass the Promise directly to your client component so Next.js can stream the page while the consent data loads in parallel.
|
|
32
|
+
>
|
|
33
|
+
> ℹ️ **Info:**
|
|
34
|
+
> For fetchInitialData(), prefer a direct backend URL such as https\://your-instance.c15t.dev. For browser-side calls from the provider, C15tPrefetch, and other client runtime work, prefer a same-origin /api/c15t rewrite. See Optimization for the full decision guide.
|
|
35
|
+
>
|
|
36
|
+
> ℹ️ **Info:**
|
|
37
|
+
> Need fully static routes? Use C15tPrefetch in your layout. Matching prefetched data is consumed automatically by the runtime instead of using fetchInitialData(). See Optimization.
|
|
38
|
+
|
|
39
|
+
### How Streaming Works
|
|
40
|
+
|
|
41
|
+
The diagram below shows how the prefetch avoids blocking the page render. The server fires the `/init` request and immediately starts streaming HTML — the resolved consent data is sent as a later chunk once the backend responds.
|
|
42
|
+
|
|
43
|
+
```mermaid
|
|
44
|
+
sequenceDiagram
|
|
45
|
+
participant SC as Server Component
|
|
46
|
+
participant NR as Next.js Runtime
|
|
47
|
+
participant BR as Browser
|
|
48
|
+
participant BE as c15t Backend
|
|
49
|
+
|
|
50
|
+
SC->>BE: fetchInitialData() → GET /init
|
|
51
|
+
Note right of SC: Returns a Promise - (not awaited)
|
|
52
|
+
SC->>NR: Render page tree - (Promise passed as prop)
|
|
53
|
+
NR->>BR: Stream initial HTML
|
|
54
|
+
BR->>BR: Hydrate — no consent data yet
|
|
55
|
+
|
|
56
|
+
BE-->>NR: /init response resolves
|
|
57
|
+
NR->>BR: Stream resolved data chunk
|
|
58
|
+
BR->>BR: Provider receives SSR data - skips client-side /init fetch
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Options
|
|
62
|
+
|
|
63
|
+
|Property|Type|Description|Default|Required|
|
|
64
|
+
|:--|:--|:--|:--|:--:|
|
|
65
|
+
|nextCache|NextCacheOptions \|undefined|Optional Next.js cache controls for SSR init requests.|-|Optional|
|
|
66
|
+
|
|
67
|
+
#### `nextCache` NextCacheOptions
|
|
68
|
+
|
|
69
|
+
Optional Next.js cache controls for SSR init requests.
|
|
70
|
+
|
|
71
|
+
|Property|Type|Description|Default|Required|
|
|
72
|
+
|:--|:--|:--|:--|:--:|
|
|
73
|
+
|revalidateSeconds|number \|false \|undefined|Cache lifetime in seconds for the Next.js data cache. Set to false to disable Next.js caching for this call.|-|Optional|
|
|
74
|
+
|
|
75
|
+
### Return Value
|
|
76
|
+
|
|
77
|
+
Returns `Promise<SSRInitialData | undefined>`. The data includes the init response (jurisdiction, translations, consent model) and GVL data when IAB is configured.
|
|
78
|
+
|
|
79
|
+
### Passing SSR Data to the Provider
|
|
80
|
+
|
|
81
|
+
Pass the unresolved Promise to the provider's `ssrData` option via a client component:
|
|
82
|
+
|
|
83
|
+
```tsx title="components/consent-manager/index.tsx"
|
|
84
|
+
'use client';
|
|
85
|
+
|
|
86
|
+
import { type ReactNode } from 'react';
|
|
87
|
+
import { ConsentManagerProvider, ConsentBanner, ConsentDialog } from '@c15t/nextjs';
|
|
88
|
+
import type { InitialDataPromise } from '@c15t/nextjs';
|
|
89
|
+
|
|
90
|
+
export default function ConsentManager({
|
|
91
|
+
children,
|
|
92
|
+
ssrData,
|
|
93
|
+
}: {
|
|
94
|
+
children: ReactNode;
|
|
95
|
+
ssrData?: InitialDataPromise;
|
|
96
|
+
}) {
|
|
97
|
+
return (
|
|
98
|
+
<ConsentManagerProvider
|
|
99
|
+
options={{
|
|
100
|
+
mode: 'hosted',
|
|
101
|
+
backendURL: '/api/c15t',
|
|
102
|
+
ssrData,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<ConsentBanner />
|
|
106
|
+
<ConsentDialog />
|
|
107
|
+
{children}
|
|
108
|
+
</ConsentManagerProvider>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```tsx title="app/layout.tsx"
|
|
114
|
+
import { fetchInitialData } from '@c15t/nextjs';
|
|
115
|
+
import ConsentManager from '@/components/consent-manager';
|
|
116
|
+
|
|
117
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
118
|
+
const ssrData = fetchInitialData({
|
|
119
|
+
backendURL: 'https://your-instance.c15t.dev',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<html lang="en">
|
|
124
|
+
<body>
|
|
125
|
+
<ConsentManager ssrData={ssrData}>
|
|
126
|
+
{children}
|
|
127
|
+
</ConsentManager>
|
|
128
|
+
</body>
|
|
129
|
+
</html>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## How Headers Are Resolved
|
|
135
|
+
|
|
136
|
+
Unlike `@c15t/react/server` where you must pass headers manually, `fetchInitialData` uses `next/headers` to automatically resolve the incoming request headers. This means geo-location headers from Vercel, Cloudflare, or AWS CloudFront are forwarded to the c15t backend without any extra configuration.
|
|
137
|
+
|
|
138
|
+
|Header|Source|Contains|
|
|
139
|
+
|--|--|--|
|
|
140
|
+
|`cf-ipcountry`|Cloudflare|Country code|
|
|
141
|
+
|`x-vercel-ip-country`|Vercel|Country code|
|
|
142
|
+
|`x-amz-cf-ipcountry`|AWS CloudFront|Country code|
|
|
143
|
+
|`x-vercel-ip-country-region`|Vercel|Region code|
|
|
144
|
+
|`accept-language`|Browser|Language preference|
|
|
145
|
+
|`x-forwarded-host`|Proxy|Original host|
|
|
146
|
+
|`x-forwarded-for`|Proxy|Client IP|
|
|
147
|
+
|
|
148
|
+
## Advanced: Using @c15t/react/server Directly
|
|
149
|
+
|
|
150
|
+
For advanced use cases (custom server frameworks, edge functions, or non-standard header resolution), the underlying utilities from `@c15t/react/server` are available:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import {
|
|
154
|
+
fetchSSRData,
|
|
155
|
+
extractRelevantHeaders,
|
|
156
|
+
normalizeBackendURL,
|
|
157
|
+
validateBackendURL,
|
|
158
|
+
} from '@c15t/react/server';
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
See the [React Server-Side Utilities](/docs/frameworks/react/server-side) docs for full API details.
|
|
162
|
+
|
|
163
|
+
## Debugging SSR
|
|
164
|
+
|
|
165
|
+
Use the [useSSRStatus](/docs/frameworks/next/hooks/use-ssr-status) hook on the client to verify SSR data was consumed:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { useSSRStatus } from '@c15t/nextjs';
|
|
169
|
+
|
|
170
|
+
function DebugSSR() {
|
|
171
|
+
const { ssrDataUsed, ssrSkippedReason } = useSSRStatus();
|
|
172
|
+
|
|
173
|
+
if (ssrDataUsed) return <span>SSR hydration successful</span>;
|
|
174
|
+
return <span>SSR skipped: {ssrSkippedReason ?? 'unknown'}</span>;
|
|
175
|
+
}
|
|
176
|
+
```
|