@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.
Files changed (98) hide show
  1. package/README.md +10 -3
  2. package/client/components/consent-dialog-link.js +3 -0
  3. package/dist/headless.cjs +1 -1
  4. package/dist/iab/styles.css +12 -0
  5. package/dist/iab/styles.tw3.css +14 -0
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/libs/browser-initial-data.cjs +1 -0
  9. package/dist/libs/browser-initial-data.js +1 -0
  10. package/dist/libs/initial-data.cjs +1 -1
  11. package/dist/libs/initial-data.js +1 -1
  12. package/dist/styles.css +10 -0
  13. package/dist/styles.tw3.css +13 -0
  14. package/dist/types.cjs +1 -1
  15. package/dist/version.cjs +1 -1
  16. package/dist/version.js +1 -1
  17. package/{dist → dist-types}/headless.d.ts +0 -1
  18. package/{dist → dist-types}/index.d.ts +3 -2
  19. package/dist-types/libs/browser-initial-data.d.ts +9 -0
  20. package/{dist → dist-types}/libs/initial-data.d.ts +7 -2
  21. package/dist-types/types.d.ts +38 -0
  22. package/dist-types/version.d.ts +1 -0
  23. package/docs/README.md +73 -0
  24. package/docs/building-headless-components.md +377 -0
  25. package/docs/callbacks.md +184 -0
  26. package/docs/components/consent-banner.md +269 -0
  27. package/docs/components/consent-dialog-link.md +59 -0
  28. package/docs/components/consent-dialog-trigger.md +103 -0
  29. package/docs/components/consent-dialog.md +177 -0
  30. package/docs/components/consent-manager-provider.md +425 -0
  31. package/docs/components/consent-widget.md +133 -0
  32. package/docs/components/dev-tools.md +63 -0
  33. package/docs/components/frame.md +73 -0
  34. package/docs/concepts/client-modes.md +175 -0
  35. package/docs/concepts/consent-categories.md +97 -0
  36. package/docs/concepts/consent-models.md +116 -0
  37. package/docs/concepts/cookie-management.md +122 -0
  38. package/docs/concepts/glossary.md +23 -0
  39. package/docs/concepts/initialization-flow.md +148 -0
  40. package/docs/concepts/policy-packs.md +229 -0
  41. package/docs/headless.md +190 -0
  42. package/docs/hooks/use-color-scheme.md +40 -0
  43. package/docs/hooks/use-consent-manager/checking-consent.md +94 -0
  44. package/docs/hooks/use-consent-manager/location-info.md +95 -0
  45. package/docs/hooks/use-consent-manager/overview.md +420 -0
  46. package/docs/hooks/use-consent-manager/setting-consent.md +92 -0
  47. package/docs/hooks/use-draggable.md +57 -0
  48. package/docs/hooks/use-focus-trap.md +41 -0
  49. package/docs/hooks/use-reduced-motion.md +35 -0
  50. package/docs/hooks/use-ssr-status.md +31 -0
  51. package/docs/hooks/use-text-direction.md +49 -0
  52. package/docs/hooks/use-translations.md +118 -0
  53. package/docs/iab/consent-banner.md +94 -0
  54. package/docs/iab/consent-dialog.md +134 -0
  55. package/docs/iab/overview.md +126 -0
  56. package/docs/iab/use-gvl-data.md +20 -0
  57. package/docs/iframe-blocking.md +107 -0
  58. package/docs/integrations/building-integrations.md +405 -0
  59. package/docs/integrations/databuddy.md +203 -0
  60. package/docs/integrations/google-tag-manager.md +153 -0
  61. package/docs/integrations/google-tag.md +122 -0
  62. package/docs/integrations/linkedin-insights.md +109 -0
  63. package/docs/integrations/meta-pixel.md +342 -0
  64. package/docs/integrations/microsoft-uet.md +112 -0
  65. package/docs/integrations/overview.md +105 -0
  66. package/docs/integrations/posthog.md +199 -0
  67. package/docs/integrations/tiktok-pixel.md +113 -0
  68. package/docs/integrations/x-pixel.md +143 -0
  69. package/docs/internationalization.md +197 -0
  70. package/docs/network-blocker.md +178 -0
  71. package/docs/optimization.md +234 -0
  72. package/docs/policy-packs.md +246 -0
  73. package/docs/quickstart.md +161 -0
  74. package/docs/script-loader.md +321 -0
  75. package/docs/server-side.md +176 -0
  76. package/docs/styling/classnames.md +92 -0
  77. package/docs/styling/color-scheme.md +82 -0
  78. package/docs/styling/css-variables.md +92 -0
  79. package/docs/styling/overview.md +456 -0
  80. package/docs/styling/slots.md +127 -0
  81. package/docs/styling/tailwind.md +113 -0
  82. package/docs/styling/tokens.md +216 -0
  83. package/docs/troubleshooting.md +146 -0
  84. package/iab/styles.css +1 -0
  85. package/package.json +38 -17
  86. package/readme.json +4 -0
  87. package/src/iab/styles.css +12 -0
  88. package/src/iab/styles.tw3.css +14 -0
  89. package/src/styles.css +10 -0
  90. package/src/styles.tw3.css +13 -0
  91. package/styles.css +1 -0
  92. package/dist/headless.d.ts.map +0 -1
  93. package/dist/index.d.ts.map +0 -1
  94. package/dist/libs/initial-data.d.ts.map +0 -1
  95. package/dist/types.d.ts +0 -16
  96. package/dist/types.d.ts.map +0 -1
  97. package/dist/version.d.ts +0 -2
  98. 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
+ ```