@c15t/nextjs 2.0.4 → 2.1.0

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 (32) hide show
  1. package/dist/version.cjs +1 -1
  2. package/dist/version.js +1 -1
  3. package/dist-types/version.d.ts +1 -1
  4. package/docs/integrations/ahrefs-analytics.md +224 -0
  5. package/docs/integrations/cloudflare-web-analytics.md +194 -0
  6. package/docs/integrations/crisp.md +214 -0
  7. package/docs/integrations/databuddy.md +136 -65
  8. package/docs/integrations/fathom-analytics.md +221 -0
  9. package/docs/integrations/google-tag-manager.md +84 -15
  10. package/docs/integrations/google-tag.md +89 -8
  11. package/docs/integrations/hotjar.md +211 -0
  12. package/docs/integrations/intercom.md +214 -0
  13. package/docs/integrations/linkedin-insights.md +130 -11
  14. package/docs/integrations/matomo-analytics.md +246 -0
  15. package/docs/integrations/meta-pixel.md +377 -24
  16. package/docs/integrations/microsoft-clarity.md +241 -0
  17. package/docs/integrations/microsoft-uet.md +120 -9
  18. package/docs/integrations/mixpanel-analytics.md +198 -0
  19. package/docs/integrations/overview.md +69 -74
  20. package/docs/integrations/plausible-analytics.md +237 -0
  21. package/docs/integrations/posthog.md +172 -41
  22. package/docs/integrations/promptwatch.md +187 -0
  23. package/docs/integrations/reddit-pixel.md +336 -0
  24. package/docs/integrations/rybbit-analytics.md +222 -0
  25. package/docs/integrations/segment.md +213 -0
  26. package/docs/integrations/snapchat-pixel.md +244 -0
  27. package/docs/integrations/tiktok-pixel.md +88 -10
  28. package/docs/integrations/umami-analytics.md +220 -0
  29. package/docs/integrations/vercel-analytics.md +213 -0
  30. package/docs/integrations/x-pixel.md +99 -10
  31. package/docs/script-loader.md +261 -63
  32. package/package.json +4 -4
@@ -1,10 +1,10 @@
1
1
  ---
2
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.
3
+ description: Gate third-party scripts behind consent in Next.js — load Google Analytics, Meta Pixel, and other tracking scripts only when users grant permission.
4
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.
5
+ The script loader manages third-party JavaScript based on consent state. You declare scripts in your provider's `scripts` option, and c15t decides when each script should load, stay loaded, unload, or receive a consent update.
6
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.
7
+ Use it for analytics, pixels, tag managers, product analytics, and other vendor snippets that should not run until the right consent condition is satisfied. Prebuilt helpers live in [`@c15t/scripts`](/docs/integrations/overview); custom scripts can be declared directly when the vendor is specific to your app.
8
8
 
9
9
  |Package manager|Command|
10
10
  |:--|:--|
@@ -14,17 +14,17 @@ c15t has a collection of premade scripts available in `@c15t/scripts`. Check the
14
14
  |bun|`bun add @c15t/scripts`|
15
15
 
16
16
  > ℹ️ **Info:**
17
- > We recommend using the pre-built integrations when possible.
17
+ > Start with the integrations overview before writing your own script. Built-in helpers encode vendor boot order, consent updates, and common defaults so you do not have to.
18
18
  >
19
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.
20
+ > If you need a vendor c15t does not ship yet, see the custom integration guide. It explains when a one-off Script is enough and when to build a reusable manifest-backed helper.
21
21
  >
22
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.
23
+ > The script loader handles JavaScript tags and callback lifecycles. For iframe-only embeds, use the iframe blocking pattern. For UI components such as maps or video players, combine consent state with a component-level placeholder or a dedicated renderable integration.
24
24
 
25
25
  ## Basic Usage
26
26
 
27
- Pass an array of `Script` objects to the provider:
27
+ Pass an array of scripts to `ConsentManagerProvider`. Built-in helpers from `@c15t/scripts` return plain `Script` objects, so they sit beside app-specific scripts:
28
28
 
29
29
  ```tsx
30
30
  import { type ReactNode } from 'react';
@@ -53,30 +53,82 @@ export function ConsentManager({ children }: { children: ReactNode }) {
53
53
  }
54
54
  ```
55
55
 
56
- ## Choose the Right Approach
56
+ The script loader runs in the browser, so place the provider in the client boundary that owns your consent UI and pass scripts through the provider options instead of injecting them in Server Components.
57
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.
58
+ ## Recommended Structure
60
59
 
61
- If you are building something reusable, start with the [custom integration guide](/docs/integrations/building-integrations) before using raw callbacks.
60
+ Define your script list once and keep it next to the consent provider:
62
61
 
63
- ## Reusable Integrations
62
+ ```tsx
63
+ 'use client';
64
64
 
65
- For app-specific use, raw `Script` objects are usually enough.
65
+ import { type ReactNode } from 'react';
66
+ import { ConsentManagerProvider } from '@c15t/nextjs';
67
+ import { gtag } from '@c15t/scripts/google-tag';
68
+ import { metaPixel } from '@c15t/scripts/meta-pixel';
66
69
 
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.
70
+ const scripts = [
71
+ gtag({ id: 'G-XXXXXXX' }),
72
+ metaPixel({ pixelId: '123456' }),
73
+ ];
68
74
 
69
- If you are building an integration for multiple apps or contributing upstream, use the [custom integration guide](/docs/integrations/building-integrations).
75
+ export function ConsentProvider({ children }: { children: ReactNode }) {
76
+ return (
77
+ <ConsentManagerProvider
78
+ options={{
79
+ mode: 'hosted',
80
+ backendURL: '/api/c15t',
81
+ scripts,
82
+ }}
83
+ >
84
+ {children}
85
+ </ConsentManagerProvider>
86
+ );
87
+ }
88
+ ```
89
+
90
+ Use environment variables or server-rendered configuration to choose ids per environment, but avoid rendering vendor scripts with `next/script` separately — that bypasses c15t's lifecycle for that vendor.
91
+
92
+ ## Mental Model
93
+
94
+ Every script you register has the same lifecycle. c15t evaluates each script against the current consent state, then drives it through a small number of states:
95
+
96
+ 1. **Pending** — registered but waiting for consent. Nothing is in the DOM yet.
97
+ 2. **Loaded** — consent matched, c15t injected the script (or ran callbacks for callback-only scripts).
98
+ 3. **Updated** — already loaded, consent state changed, `onConsentChange` ran so the SDK can react.
99
+ 4. **Unloaded** — consent was revoked. c15t removed the script element unless you opted into persistence.
100
+
101
+ Four lifecycle callbacks let you hook into transitions: `onBeforeLoad`, `onLoad`, `onConsentChange`, and `onError`. Two flags — [`alwaysLoad`](#always-load) and [`persistAfterConsentRevoked`](#persist-after-revocation) — change how c15t treats consent boundaries. Everything else (DOM placement, ad-block evasion, dynamic management) is a refinement on top of this core model.
102
+
103
+ ## Choose the Right Approach
104
+
105
+ Most projects mix more than one style. Pick the smallest one that keeps consent behavior obvious:
106
+
107
+ |Style|Use when|
108
+ |--|--|
109
+ |**Built-in helper** from `@c15t/scripts`|c15t already ships the vendor. See the [integrations overview](/docs/integrations/overview).|
110
+ |**Plain `Script`**|One-off app code with simple load and callback behavior.|
111
+ |**Callback-only `Script`**|Another package already loaded the SDK; c15t only synchronizes consent.|
112
+ |**Manifest-backed helper**|Reusable vendor integration with structured setup phases, queues, stubs, or a vendor consent API.|
113
+ |**Iframe / renderable integration**|Vendor exposes an iframe or React component, not just a `<script>` tag.|
70
114
 
71
115
  ## Script Types
72
116
 
73
117
  ### Standard Scripts
74
118
 
75
- Load an external JavaScript file via a `<script>` tag. Use `src` to specify the URL.
119
+ Standard scripts load an external JavaScript file via a `<script>` tag. This is the default for most analytics and pixel SDKs:
120
+
121
+ ```tsx
122
+ {
123
+ id: 'analytics',
124
+ src: 'https://cdn.example.com/analytics.js',
125
+ category: 'measurement',
126
+ }
127
+ ```
76
128
 
77
129
  ### Inline Scripts
78
130
 
79
- Execute inline JavaScript code. Use `textContent` instead of `src`:
131
+ Inline scripts execute JavaScript from `textContent` instead of loading a URL. Use these sparingly; a manifest-backed helper is usually better for reusable vendor code.
80
132
 
81
133
  ```tsx
82
134
  {
@@ -93,7 +145,7 @@ Execute inline JavaScript code. Use `textContent` instead of `src`:
93
145
 
94
146
  ### Callback-Only Scripts
95
147
 
96
- Don't inject any `<script>` tag - just execute callbacks based on consent changes. Useful for controlling libraries that are already loaded:
148
+ Callback-only scripts do not inject a script tag. They run lifecycle callbacks when consent allows them to. Use this when another package has already loaded the SDK and c15t only needs to drive consent:
97
149
 
98
150
  ```tsx
99
151
  {
@@ -115,31 +167,34 @@ Don't inject any `<script>` tag - just execute callbacks based on consent change
115
167
  }
116
168
  ```
117
169
 
118
- ## Consent Conditions
170
+ ### Manifest-Backed Helpers
119
171
 
120
- The `category` field accepts a `HasCondition` - either a simple string or a logical expression:
172
+ Built-in integrations in `@c15t/scripts` are manifest-backed. A manifest describes vendor setup as structured phases, then c15t compiles it into a `Script`. Manifests keep queue stubs, script URLs, consent signaling, and post-load work consistent across apps and they are safe to ship from a server.
121
173
 
122
- ```tsx
123
- // Simple: requires measurement consent
124
- { category: 'measurement' }
174
+ Use a manifest-backed helper when:
125
175
 
126
- // AND: requires both measurement and marketing
127
- { category: { and: ['measurement', 'marketing'] } }
176
+ * the integration should be reused across projects,
177
+ * the vendor snippet has ordered setup steps,
178
+ * the vendor exposes a consent API,
179
+ * or you plan to contribute the integration back to c15t.
128
180
 
129
- // OR: requires either measurement or marketing
130
- { category: { or: ['measurement', 'marketing'] } }
131
- ```
181
+ Read the [custom integration guide](/docs/integrations/building-integrations) for the manifest contract, phases, and testing checklist.
132
182
 
133
- ## Script Callbacks
183
+ ### Iframe And Renderable Integrations
134
184
 
135
- Every script supports four lifecycle callbacks:
185
+ Some vendors are not just script tags. YouTube embeds, maps, calendars, and checkout widgets often need a visible component, a placeholder, or an iframe.
136
186
 
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|
187
+ * For iframe-only embeds, gate the iframe `src` with the [iframe blocking](/docs/frameworks/react/iframe-blocking) pattern instead of loading a script just to hide an iframe.
188
+ * For SDK-backed UI, use the script loader for the shared SDK and render the component only when consent and SDK readiness agree.
189
+
190
+ ## Lifecycle Callbacks
191
+
192
+ Every script supports four callbacks. Each receives a `ScriptCallbackInfo` payload (id, element, hasConsent, consents):
193
+
194
+ * `onBeforeLoad` — runs before the script tag is injected. Create globals, queues, or vendor stubs here.
195
+ * `onLoad` — runs after the browser loads the script. Call vendor `init()` APIs here.
196
+ * `onConsentChange` — runs for loaded scripts when consent changes. Forward the new consent state to the vendor SDK.
197
+ * `onError` — runs when the script fails to load. Record diagnostics or render a fallback.
143
198
 
144
199
  ```tsx
145
200
  {
@@ -147,38 +202,60 @@ Every script supports four lifecycle callbacks:
147
202
  src: 'https://analytics.example.com/v2.js',
148
203
  category: 'measurement',
149
204
  onBeforeLoad: ({ id }) => {
150
- console.log(`Loading script: ${id}`);
205
+ window.analyticsQueue = window.analyticsQueue || [];
151
206
  },
152
- onLoad: ({ element }) => {
207
+ onLoad: () => {
153
208
  window.analytics.init('my-key');
154
209
  },
155
210
  onError: ({ error }) => {
156
211
  console.error('Failed to load analytics:', error);
157
212
  },
158
- onConsentChange: ({ hasConsent, consents }) => {
213
+ onConsentChange: ({ hasConsent }) => {
159
214
  window.analytics.setConsent(hasConsent);
160
215
  },
161
216
  }
162
217
  ```
163
218
 
164
- ## Advanced Options
219
+ ## Consent Conditions
220
+
221
+ The `category` field accepts a `HasCondition`. It can be a single consent category or a logical expression:
222
+
223
+ ```tsx
224
+ // Simple: requires measurement consent
225
+ { category: 'measurement' }
226
+
227
+ // AND: requires both measurement and marketing
228
+ { category: { and: ['measurement', 'marketing'] } }
229
+
230
+ // OR: requires either measurement or marketing
231
+ { category: { or: ['measurement', 'marketing'] } }
232
+ ```
233
+
234
+ Consent categories use the same names as the rest of c15t (`necessary`, `functionality`, `experience`, `measurement`, `marketing`).
235
+
236
+ ## Persistence Options
165
237
 
166
238
  ### Always Load
167
239
 
168
- Scripts that manage their own consent internally (like GTM in consent mode):
240
+ `alwaysLoad` loads the script regardless of whether its category is currently granted. Use it only when the vendor must be present early **and** has a reliable consent API of its own — Google Tag Manager with Consent Mode is the canonical example.
169
241
 
170
242
  ```tsx
171
243
  {
172
244
  id: 'google-tag-manager',
173
245
  src: 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXX',
174
246
  category: 'measurement',
175
- alwaysLoad: true, // Loads regardless of consent state
247
+ alwaysLoad: true,
176
248
  }
177
249
  ```
178
250
 
251
+ When `alwaysLoad` is on, `onConsentChange` becomes mandatory: it is how the loaded SDK learns about every transition.
252
+
253
+ > ⚠️ **Warning:**
254
+ > alwaysLoad shifts compliance responsibility to the vendor integration. Make sure the script receives denied-by-default consent signals before it can track.
255
+
179
256
  ### Persist After Revocation
180
257
 
181
- Keep the script loaded even after consent is revoked (the page won't reload for this script):
258
+ `persistAfterConsentRevoked` keeps a script in the page after consent is revoked instead of unloading it. Use it only when the vendor exposes a runtime consent toggle — otherwise unloading is safer because removing the element guarantees the SDK stops.
182
259
 
183
260
  ```tsx
184
261
  {
@@ -186,59 +263,180 @@ Keep the script loaded even after consent is revoked (the page won't reload for
186
263
  src: 'https://errors.example.com/track.js',
187
264
  category: 'measurement',
188
265
  persistAfterConsentRevoked: true,
266
+ onConsentChange: ({ hasConsent }) => {
267
+ window.ErrorTracker.setConsent(hasConsent);
268
+ },
189
269
  }
190
270
  ```
191
271
 
192
- ### Script Placement
272
+ As with `alwaysLoad`, `onConsentChange` is how the persisted SDK learns about consent updates.
273
+
274
+ ### `alwaysLoad` vs `persistAfterConsentRevoked`
275
+
276
+ These two flags answer different questions. Use this table to keep them straight:
277
+
278
+ |Question|`alwaysLoad`|`persistAfterConsentRevoked`|
279
+ |--|--|--|
280
+ |Loads before consent is granted?|Yes|No (waits for consent like a normal script)|
281
+ |Stays loaded after consent is revoked?|Yes|Yes|
282
+ |Requires a vendor consent API?|Yes|Yes|
283
+
284
+ ## DOM Placement
193
285
 
194
- Control where in the DOM the script is injected:
286
+ Control where the script is injected and whether the element id is anonymized:
195
287
 
196
288
  ```tsx
197
289
  {
198
290
  id: 'widget',
199
291
  src: 'https://widget.example.com/embed.js',
200
292
  category: 'experience',
201
- target: 'body', // 'head' (default) or 'body'
293
+ target: 'body', // 'head' (default) or 'body'
294
+ anonymizeId: true, // default: true, hides the c15t script id from ad blockers
295
+ nonce: 'abc123', // optional CSP nonce
202
296
  }
203
297
  ```
204
298
 
205
- ### Ad Blocker Evasion
299
+ Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
206
300
 
207
- Script element IDs are anonymized by default to avoid ad blocker pattern matching:
301
+ ## Dynamic Management
302
+
303
+ Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
304
+
305
+ * `setScripts(scripts)` — registers script definitions and immediately evaluates them against consent.
306
+ * `removeScript(id)` — removes a definition and unloads its element if needed.
307
+ * `isScriptLoaded(id)` — returns whether c15t has loaded a script.
308
+ * `getLoadedScriptIds()` — returns every currently loaded script id.
309
+
310
+ Dynamic scripts should still use stable ids. If the same vendor is added repeatedly with different ids, c15t treats each call as a new script.
311
+
312
+ ## Calling Vendor APIs From Your App
313
+
314
+ The script loader controls **when the vendor SDK loads**. It does not intercept calls your application code makes to that SDK afterwards. Whether your event calls are safe before consent is granted depends on the script's persistence flags:
315
+
316
+ |Vendor pattern|What c15t does|What your app code must do|
317
+ |--|--|--|
318
+ |Consent-gated load, unloaded on revoke (e.g. cookieless analytics)|Script not in DOM until consent granted; removed on revoke. Global is `undefined` outside that window.|**Guard every call.** Unguarded `window.vendor.track(...)` throws when the global is absent.|
319
+ |Consent-gated load with `persistAfterConsentRevoked` (e.g. Meta Pixel)|Script not in DOM until consent granted; stays after revoke. c15t calls vendor's consent-revoke API on revocation.|Guard calls only for the pre-initial-consent window. Once loaded, the SDK handles its own suppression.|
320
+ |`alwaysLoad: true` with a vendor consent API (e.g. GTM, gtag, Databuddy, PostHog)|Script in DOM on page start; c15t signals consent state through the vendor's API.|Calls are safe — the vendor SDK suppresses transmission when consent is denied.|
321
+ |No app-facing API (e.g. Cloudflare Web Analytics)|Script in/out of DOM based on consent. Tracking is fully automatic.|Nothing to guard.|
322
+
323
+ The safe pattern in React is to read consent state through `useConsentManager().has(category)` before calling the SDK:
208
324
 
209
325
  ```tsx
210
- {
211
- id: 'analytics',
212
- src: '...',
213
- category: 'measurement',
214
- anonymizeId: true, // default: true
326
+ import { useCallback } from 'react';
327
+ import { useConsentManager } from '@c15t/react';
328
+
329
+ function useTrackSignup() {
330
+ const { has } = useConsentManager();
331
+
332
+ return useCallback(() => {
333
+ if (has('measurement')) {
334
+ window.fathom?.trackEvent('signup');
335
+ }
336
+ }, [has]);
337
+ }
338
+
339
+ function SignupButton() {
340
+ const trackSignup = useTrackSignup();
341
+
342
+ return <button onClick={trackSignup}>Sign up</button>;
343
+ }
344
+ ```
345
+
346
+ From non-React code, read the consent store directly:
347
+
348
+ ```ts
349
+ import { getOrCreateConsentRuntime } from 'c15t';
350
+
351
+ const { consentStore } = getOrCreateConsentRuntime();
352
+
353
+ if (consentStore.getState().has('measurement')) {
354
+ window.fathom?.trackEvent('signup');
215
355
  }
216
356
  ```
217
357
 
358
+ Each [integration page](/docs/integrations/overview) includes a vendor-specific **Tracking events in your app** block that names which pattern applies.
359
+
360
+ ## Debugging Checklist
361
+
362
+ When a script does not behave as expected:
363
+
364
+ 1. Confirm the script's `category` matches the consent that has been granted.
365
+ 2. Check whether the script is `alwaysLoad` or consent-gated.
366
+ 3. Confirm `onBeforeLoad` creates any globals before the vendor code reads them.
367
+ 4. Confirm `onConsentChange` updates persisted or always-loaded scripts when consent changes.
368
+ 5. Check whether the browser or an ad blocker blocked the request.
369
+ 6. Use c15t devtools to inspect script lifecycle events when available.
370
+
218
371
  ## Dynamic Script Management
219
372
 
220
- Add, remove, or check scripts at runtime via `useConsentManager()`:
373
+ The shared guide above lists what the script-manager methods do. In Next.js they are exposed through `useConsentManager()`:
221
374
 
222
375
  ```tsx
223
376
  import { useConsentManager } from '@c15t/nextjs';
224
377
 
225
378
  function ScriptManager() {
226
- const { setScripts, removeScript, isScriptLoaded, getLoadedScriptIds } = useConsentManager();
379
+ const {
380
+ setScripts,
381
+ removeScript,
382
+ isScriptLoaded,
383
+ getLoadedScriptIds,
384
+ } = useConsentManager();
385
+
386
+ // ...
387
+ }
388
+ ```
389
+
390
+ Register dynamic scripts from a client effect or event handler — never directly in the render body — so React can run the call once per dependency change and tear it down on unmount:
391
+
392
+ ```tsx
393
+ 'use client';
394
+
395
+ import { useEffect } from 'react';
396
+ import { useConsentManager } from '@c15t/nextjs';
397
+
398
+ export function WorkspaceAnalytics({ workspaceId }: { workspaceId: string }) {
399
+ const { setScripts, removeScript } = useConsentManager();
227
400
 
228
- // Add scripts dynamically
229
- setScripts([{ id: 'dynamic', src: '...', category: 'measurement' }]);
401
+ useEffect(() => {
402
+ const scriptId = `workspace-analytics-${workspaceId}`;
230
403
 
231
- // Remove a script
232
- removeScript('dynamic');
404
+ setScripts([
405
+ {
406
+ id: scriptId,
407
+ src: `https://cdn.example.com/workspaces/${workspaceId}.js`,
408
+ category: 'measurement',
409
+ },
410
+ ]);
233
411
 
234
- // Check if a script is loaded
235
- const loaded = isScriptLoaded('google-analytics');
412
+ return () => {
413
+ removeScript(scriptId);
414
+ };
415
+ }, [workspaceId, setScripts, removeScript]);
236
416
 
237
- // Get all loaded script IDs
238
- const allLoaded = getLoadedScriptIds();
417
+ return null;
239
418
  }
240
419
  ```
241
420
 
421
+ ## Renderable Integrations
422
+
423
+ Some integrations need a visible component, not just a script tag. Google Maps, YouTube, checkout widgets, and calendars all need consent gating plus component lifecycle.
424
+
425
+ Split the problem into three layers:
426
+
427
+ 1. Use the script loader as the source of truth for shared SDK loading.
428
+ 2. Render a placeholder until consent is granted.
429
+ 3. Create the widget instance only on the client and clean it up on unmount.
430
+
431
+ For iframe-only embeds, use the [iframe blocking](/docs/frameworks/next/iframe-blocking) pattern. For SDK-backed widgets, load the SDK once and let each component instance create and clean up its own widget. Avoid mixing `next/script` with c15t for the same vendor.
432
+
433
+ ## App Router Notes
434
+
435
+ * The provider and any component that calls `useConsentManager()` must be client components.
436
+ * Keep vendor ids out of static examples when they differ per environment — read them from `process.env.NEXT_PUBLIC_*` or runtime config.
437
+ * If a script must be available before a page becomes interactive, prefer a built-in helper that models denied-consent defaults rather than adding a separate `next/script` tag.
438
+ * If you use CSP nonces, pass the nonce through the `Script` object so c15t applies it to the generated script element.
439
+
242
440
  ## API Reference
243
441
 
244
442
  ### Script
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c15t/nextjs",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "Headless cookie banner, consent manager & preference center for Next.js. App Router & Pages Router, RSC, GDPR/CCPA/LGPD/TCF compliant.",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -97,9 +97,9 @@
97
97
  "test:watch": "bun prebuild && vitest --passWithNoTests"
98
98
  },
99
99
  "dependencies": {
100
- "@c15t/react": "2.0.4",
101
- "@c15t/translations": "2.0.0",
102
- "c15t": "2.0.4"
100
+ "@c15t/react": "2.1.0",
101
+ "@c15t/translations": "2.1.0",
102
+ "c15t": "2.1.0"
103
103
  },
104
104
  "devDependencies": {
105
105
  "@c15t/typescript-config": "0.0.1",