@c15t/react 3.0.0-alpha.8 → 3.0.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 (164) hide show
  1. package/AGENTS.md +10 -12
  2. package/README.md +41 -70
  3. package/SKILL.md +1 -1
  4. package/dist/aggregate-components.js +1 -1
  5. package/dist/components/consent-gate/atoms.js +1 -1
  6. package/dist/components/consent-gate/frame.js +2 -0
  7. package/dist/components/consent-gate/index.js +1 -1
  8. package/dist/components/iab-panel/atoms/root.js +1 -1
  9. package/dist/components/iab-panel/iab-panel.js +1 -1
  10. package/dist/components/iab-prompt/atoms/root.js +1 -1
  11. package/dist/components/iab-prompt/iab-prompt.js +1 -1
  12. package/dist/components/panel/atoms/root.js +1 -1
  13. package/dist/components/panel-trigger/atoms/root.js +1 -1
  14. package/dist/components/panel-trigger/panel-trigger.js +1 -1
  15. package/dist/components/panel-trigger/toolbar-loader.js +2 -0
  16. package/dist/components/preferences/atoms/root.js +1 -1
  17. package/dist/components/prompt/atoms/early-tap-script.js +2 -0
  18. package/dist/components/prompt/atoms/root.js +1 -1
  19. package/dist/components/prompt/prompt.js +1 -1
  20. package/dist/components/shared/dialog-sheets.js +1 -0
  21. package/dist/components/shared/first-paint-sheets.js +1 -0
  22. package/dist/components/shared/iab-dialog-sheets.js +1 -0
  23. package/dist/components/shared/iab-first-paint-sheets.js +1 -0
  24. package/dist/components/shared/primitives/button.js +1 -1
  25. package/dist/components/shared/surface-styles.js +2 -0
  26. package/dist/consent-gate.js +1 -1
  27. package/dist/context/index.js +1 -1
  28. package/dist/devtools.js +1 -1
  29. package/dist/hooks/use-iab-policy-guard.js +1 -0
  30. package/dist/iab/styles.tw3.css +2 -2
  31. package/dist/iab-context.js +1 -1
  32. package/dist/iab-dialog-deferred.js +2 -0
  33. package/dist/iab.js +1 -1
  34. package/dist/index-parts/consent-gate.js +1 -1
  35. package/dist/index-parts/core.js +1 -1
  36. package/dist/index-parts/frame.js +1 -0
  37. package/dist/index-parts/hosted.js +1 -0
  38. package/dist/index-parts/offline.js +1 -0
  39. package/dist/index-parts/presets.js +1 -0
  40. package/dist/index.js +1 -1
  41. package/dist/modes.js +1 -0
  42. package/dist/panel-link.js +1 -1
  43. package/dist/panel-trigger.js +1 -1
  44. package/dist/provider.js +1 -1
  45. package/dist/styles.tw3.css +2 -2
  46. package/dist/transports/hosted.js +1 -0
  47. package/dist/transports/manifest.js +1 -0
  48. package/dist/utils/deferred-module.js +1 -1
  49. package/dist/version.js +1 -1
  50. package/dist-types/chunk-warming.d.ts +7 -6
  51. package/dist-types/components/consent-gate/frame.d.ts +19 -0
  52. package/dist-types/components/consent-gate/index.d.ts +3 -19
  53. package/dist-types/components/iab-prompt/iab-prompt.d.ts +1 -1
  54. package/dist-types/components/panel/index.d.ts +1 -1
  55. package/dist-types/components/panel-trigger/toolbar-loader.d.ts +34 -0
  56. package/dist-types/components/preferences/index.d.ts +1 -1
  57. package/dist-types/components/prompt/atoms/early-tap-script.d.ts +17 -0
  58. package/dist-types/components/prompt/index.d.ts +1 -1
  59. package/dist-types/components/shared/dialog-sheets.d.ts +9 -0
  60. package/dist-types/components/shared/first-paint-sheets.d.ts +8 -0
  61. package/dist-types/components/shared/iab-dialog-sheets.d.ts +3 -0
  62. package/dist-types/components/shared/iab-first-paint-sheets.d.ts +3 -0
  63. package/dist-types/components/shared/surface-styles.d.ts +60 -0
  64. package/dist-types/consent-gate.d.ts +1 -1
  65. package/dist-types/context/index.d.ts +3 -3
  66. package/dist-types/context.d.ts +2 -0
  67. package/dist-types/gpp.d.ts +1 -1
  68. package/dist-types/hooks/use-iab-policy-guard.d.ts +35 -0
  69. package/dist-types/iab-dialog-deferred.d.ts +36 -0
  70. package/dist-types/iab.d.ts +3 -2
  71. package/dist-types/index-parts/consent-gate.d.ts +1 -1
  72. package/dist-types/index-parts/core.d.ts +1 -2
  73. package/dist-types/index-parts/frame.d.ts +7 -0
  74. package/dist-types/index-parts/hosted.d.ts +7 -0
  75. package/dist-types/index-parts/offline.d.ts +7 -0
  76. package/dist-types/index-parts/presets.d.ts +7 -0
  77. package/dist-types/index.d.ts +5 -1
  78. package/dist-types/modes.d.ts +26 -0
  79. package/dist-types/panel-link.d.ts +1 -1
  80. package/dist-types/panel-trigger.d.ts +1 -1
  81. package/dist-types/provider.d.ts +22 -4
  82. package/dist-types/transports/hosted.d.ts +36 -0
  83. package/dist-types/transports/manifest.d.ts +48 -0
  84. package/dist-types/ui-config-context.d.ts +4 -0
  85. package/dist-types/version.d.ts +1 -1
  86. package/docs/README.md +10 -12
  87. package/docs/concepts/choose-your-setup.md +45 -8
  88. package/docs/concepts/consent-categories.md +1 -1
  89. package/docs/concepts/consent-state.md +5 -2
  90. package/docs/concepts/data-fetching.md +100 -40
  91. package/docs/concepts/modes.md +281 -0
  92. package/docs/customization/dark-mode.md +1 -1
  93. package/docs/customization/motion.md +2 -2
  94. package/docs/customization/overview.md +3 -2
  95. package/docs/customization/recipes.md +4 -4
  96. package/docs/customization/slots.md +25 -25
  97. package/docs/customization/stylesheets.md +161 -50
  98. package/docs/customization/tailwind.md +71 -50
  99. package/docs/customization/tokens.md +42 -13
  100. package/docs/frameworks/react/components/consent-banner.md +14 -12
  101. package/docs/frameworks/react/components/consent-dialog.md +15 -13
  102. package/docs/frameworks/react/components/consent-provider.md +51 -46
  103. package/docs/frameworks/react/components.md +16 -13
  104. package/docs/frameworks/react/compose.md +3 -3
  105. package/docs/frameworks/react/content-security-policy.md +35 -12
  106. package/docs/frameworks/react/customize.md +21 -13
  107. package/docs/frameworks/react/hooks.md +2 -2
  108. package/docs/frameworks/react/iab.md +56 -6
  109. package/docs/frameworks/react/quickstart.md +65 -99
  110. package/docs/frameworks/react/rendering.md +170 -94
  111. package/docs/frameworks/react/scripts.md +19 -30
  112. package/docs/frameworks/react/troubleshooting.md +100 -32
  113. package/docs/frameworks/react/upgrade-v3.md +133 -79
  114. package/docs/frameworks/react/vendor-consent.md +19 -7
  115. package/docs/guides/banner-experiments.md +21 -8
  116. package/docs/guides/troubleshooting.md +90 -26
  117. package/docs/guides/verify-consent.md +23 -8
  118. package/docs/integrations/adobe-analytics.md +60 -64
  119. package/docs/integrations/ahrefs-analytics.md +58 -63
  120. package/docs/integrations/amplitude.md +60 -64
  121. package/docs/integrations/clearbit.md +59 -63
  122. package/docs/integrations/cloudflare-web-analytics.md +56 -60
  123. package/docs/integrations/cloudflare-zaraz.md +55 -57
  124. package/docs/integrations/crisp.md +64 -69
  125. package/docs/integrations/databuddy.md +62 -64
  126. package/docs/integrations/fathom-analytics.md +56 -60
  127. package/docs/integrations/front-chat.md +56 -60
  128. package/docs/integrations/google-maps.md +25 -13
  129. package/docs/integrations/google-tag-manager.md +153 -77
  130. package/docs/integrations/google-tag.md +134 -78
  131. package/docs/integrations/heap.md +56 -60
  132. package/docs/integrations/hightouch.md +55 -60
  133. package/docs/integrations/hotjar.md +60 -64
  134. package/docs/integrations/intercom.md +55 -60
  135. package/docs/integrations/klaviyo.md +65 -66
  136. package/docs/integrations/linkedin-insights.md +58 -63
  137. package/docs/integrations/logrocket.md +55 -60
  138. package/docs/integrations/matomo-analytics.md +58 -60
  139. package/docs/integrations/meta-pixel.md +56 -60
  140. package/docs/integrations/microsoft-clarity.md +57 -60
  141. package/docs/integrations/microsoft-uet.md +58 -61
  142. package/docs/integrations/mixpanel-analytics.md +73 -75
  143. package/docs/integrations/one-dollar-stats.md +53 -59
  144. package/docs/integrations/openai-pixel.md +58 -60
  145. package/docs/integrations/overview.md +59 -42
  146. package/docs/integrations/pinterest-tag.md +56 -60
  147. package/docs/integrations/pirsch.md +54 -60
  148. package/docs/integrations/plausible-analytics.md +55 -60
  149. package/docs/integrations/posthog.md +56 -60
  150. package/docs/integrations/promptwatch.md +57 -63
  151. package/docs/integrations/reddit-pixel.md +57 -60
  152. package/docs/integrations/rudderstack.md +65 -67
  153. package/docs/integrations/rybbit-analytics.md +70 -75
  154. package/docs/integrations/segment.md +56 -60
  155. package/docs/integrations/sentry.md +65 -60
  156. package/docs/integrations/snapchat-pixel.md +54 -60
  157. package/docs/integrations/tiktok-pixel.md +59 -63
  158. package/docs/integrations/umami-analytics.md +54 -60
  159. package/docs/integrations/vercel-analytics.md +53 -59
  160. package/docs/integrations/vercel-speed-insights.md +317 -0
  161. package/docs/integrations/x-pixel.md +57 -63
  162. package/docs/integrations/youtube.md +25 -13
  163. package/package.json +18 -8
  164. package/docs/upgrade-v3.md +0 -429
@@ -22,6 +22,10 @@ TC string instead; see [IAB TCF](./iab.md).
22
22
 
23
23
  ## Declare the vendors
24
24
 
25
+ Built-in `@c15t/integrations` helpers already list their vendor by name, so
26
+ you only declare a vendor for a script of your own, or to replace a helper's
27
+ name and privacy policy.
28
+
25
29
  Declare the vendors next to the scripts in `src/scripts.ts`, using the `vendor`
26
30
  slug each script already carries:
27
31
 
@@ -66,11 +70,19 @@ helper sets `vendor` to its script ID, so `xPixel()` is `x-pixel`, `gtag()`
66
70
  is `gtag` and `cloudflareZaraz()` is `cloudflare-zaraz`. Each
67
71
  [vendor guide](../../integrations/overview.md) names its slug.
68
72
 
69
- Declaring a helper's vendor is optional. Without a declaration the script
70
- still loads with its category, but the dialog has no switch for it. In
71
- development, c15t logs a console warning that names the slug, so you can
72
- declare it if visitors should be able to turn it off. Production builds
73
- skip the warning.
73
+ You don't need to declare a helper's vendor. Each helper also sets
74
+ `vendorDetails` on its script: the vendor's name, privacy policy, homepage
75
+ and legal entity. The dialog lists the vendor with its own switch from
76
+ those. Declare the vendor in `vendors` to replace them, for example to link
77
+ your own data processing notice, or when you self-host a tool such as
78
+ Matomo, Umami, Plausible or PostHog and the vendor's privacy policy doesn't
79
+ cover your install. A declaration replaces `vendorDetails` as a whole and
80
+ doesn't merge with it.
81
+
82
+ Your own scripts can set `vendorDetails` too. A script with a `vendor` slug
83
+ and no `name` and `privacyPolicyUrl` from any source still loads with its
84
+ category, but the dialog has no switch for it. In development, c15t logs a
85
+ console warning that names the slug. Production builds skip the warning.
74
86
 
75
87
  A self-hosted backend can declare vendors too. `/init` returns them and c15t
76
88
  merges them with the vendors in code. When both declare the same `id`, the
@@ -165,8 +177,8 @@ the same as withdrawing a category, so code the vendor already ran stops.
165
177
  Test the production build in a private window with DevTools Network open and
166
178
  filtered to `ads-twitter.com`:
167
179
 
168
- 1. Declare `x-pixel` and register `xPixel()`. Open Privacy settings. The
169
- marketing row lists X Pixel with a switch.
180
+ 1. Register `xPixel()`. Open Privacy settings. The marketing row lists X
181
+ Pixel with a switch.
170
182
  2. Allow marketing and save. `uwt.js` loads.
171
183
  3. Open Privacy settings, switch X Pixel off and save. c15t reloads the
172
184
  page. After the reload there is no `uwt.js` request, and other marketing
@@ -32,9 +32,7 @@ import type { ReactNode } from 'react';
32
32
 
33
33
  import { scripts } from './scripts';
34
34
 
35
- import 'c15t/react/styles.css';
36
-
37
- const mode = hosted({ url: 'https://your-project.inth.app' });
35
+ const mode = hosted({ backendURL: 'https://your-project.inth.app' });
38
36
 
39
37
  // `control` is the stock banner. `wall` blocks the page until the visitor
40
38
  // chooses.
@@ -258,7 +256,6 @@ import { resolveConsent } from 'c15t/next/server';
258
256
  import { Suspense } from 'react';
259
257
  import type { ReactNode } from 'react';
260
258
 
261
- import { consentOptions } from '@/c15t.server';
262
259
  import { ExperimentConsent } from '@/components/experiment-consent';
263
260
  import { bannerExperiment } from '@/lib/experiment';
264
261
  import { bannerExperimentFlag } from '@/lib/flags';
@@ -268,7 +265,6 @@ import '@/styles/globals.css';
268
265
  const ResolvedConsent = async ({ children }: { children: ReactNode }) => {
269
266
  const arm = await bannerExperimentFlag();
270
267
  const state = await resolveConsent({
271
- ...consentOptions,
272
268
  // `off` keeps this visitor out of the experiment.
273
269
  experiment: arm === 'off' ? undefined : { ...bannerExperiment, arm },
274
270
  });
@@ -401,6 +397,22 @@ arm re-assigns only the visitors who were in it. For a clean analysis, change
401
397
  Built-in assignment is not available in Astro; see
402
398
  [Astro](#astro).
403
399
 
400
+ A Vite app's early `/init` script, from `consentManifest()`, picks the arm
401
+ the same way when you pass it the experiment, and the app then runs that
402
+ arm. Import the same object into `vite.config.ts`:
403
+
404
+ ```ts title="vite.config.ts"
405
+ consentManifest({ initPrefetch: { experiment: bannerExperiment } });
406
+ ```
407
+
408
+ Without it, the app sends its own `/init` for visitors who have not chosen
409
+ yet. The same goes for `consentPrefetchHead` in TanStack Start and
410
+ `buildPrefetchScript` elsewhere, which take the same `experiment` option.
411
+
412
+ When a feature flag picks the arm, leave `experiment` out of
413
+ `initPrefetch`. The script would pick its own arm and send it to the
414
+ backend, and the app would then send a second `/init` with the flag's arm.
415
+
404
416
  ## Acknowledge presentation diagnostics
405
417
 
406
418
  Each arm is resolved under the visitor's policy the same way `presentation`
@@ -494,11 +506,12 @@ owed to, and how many of them accepted. c15t sends both to the backend on
494
506
  its own; you do not wire anything up.
495
507
 
496
508
  * **Visitors owed the banner.** While a visitor has no stored choice, every
497
- `/init` carries their arm in an `x-c15t-experiment: <id>=<arm>` header,
498
- and the backend puts it on that request's session report as
509
+ `/init` carries their arm as `<id>=<arm>` in the `experiment` query
510
+ parameter, and the backend puts it on that request's session report as
499
511
  `experiment: { id, arm }`. In manifest mode, the server render or the
500
512
  init route puts it on the report it sends to `POST /sessions`. A visitor
501
- who already chose is not shown the banner, so is not counted.
513
+ who already chose is not shown the banner, so is not counted. A
514
+ server-to-server caller can send the `x-c15t-experiment` header instead.
502
515
  * **Choices.** Every choice saved through `POST /subjects` carries the arm
503
516
  in `metadata.experiment`, as above.
504
517
 
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  title: Troubleshooting
3
3
  description: Fix a missing banner, analytics that load before consent, choices
4
- lost on reload, CORS errors, hydration differences and failed static builds in
5
- c15t v3.
4
+ lost on reload, CORS errors, hydration differences, failed manifest downloads,
5
+ mode errors and failed static builds in c15t v3.
6
6
  group: guides
7
7
  ---
8
8
 
@@ -35,12 +35,12 @@ defined by exports" or "Module not found".
35
35
 
36
36
  Install the v3 release and check the lockfile:
37
37
 
38
- | Package manager | Command |
39
- | :-------------- | :----------------------- |
40
- | npm | `npm install c15t@alpha` |
41
- | pnpm | `pnpm add c15t@alpha` |
42
- | yarn | `yarn add c15t@alpha` |
43
- | bun | `bun add c15t@alpha` |
38
+ | Package manager | Command |
39
+ | :-------------- | :----------------- |
40
+ | npm | `npm install c15t` |
41
+ | pnpm | `pnpm add c15t` |
42
+ | yarn | `yarn add c15t` |
43
+ | bun | `bun add c15t` |
44
44
 
45
45
  Keep every c15t package, including `@c15t/integrations`, on the same release.
46
46
  Mixing v2 and v3 packages is not supported.
@@ -49,14 +49,14 @@ Mixing v2 and v3 packages is not supported.
49
49
 
50
50
  No banner is sometimes correct. Find out which case you have:
51
51
 
52
- | What DevTools shows | Cause | Fix |
53
- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
54
- | The backend request failed or has a CORS error | Wrong backend URL, or your origin is not trusted | Copy the URL from your Inth project exactly, including any path. Add the site's origin, such as `https://www.example.com` or `http://localhost:3000`, to the project's trusted origins. |
55
- | Requests go to `your-project.inth.app` and fail | The placeholder backend URL from the guides is still in your code | Replace `https://your-project.inth.app` with the URL from your Inth project, including any path prefix. |
56
- | No backend request at all | No provider mounted, or the provider runs in offline mode | Check `window.c15t`. Confirm the provider receives `hosted()` with your backend URL, or the `backendURL` option your framework uses. |
57
- | The policy resolved and no prompt is needed | The visitor's location maps to a policy without a prompt, such as a US state without a privacy law in the recommended rules | Expected. Test from a location that needs a prompt. Server helpers such as Next.js `resolveConsent` accept a `country` override for testing; remove it before you deploy. |
58
- | The policy resolved and a choice is stored | A returning visitor | Expected. Clear site data or use a private window. |
59
- | The banner is in the DOM but invisible or unstyled | The stylesheet is missing, or loaded before Tailwind CSS 4 | Import your framework's c15t stylesheet from your global CSS. See [customization](../customization/overview.md). |
52
+ | What DevTools shows | Cause | Fix |
53
+ | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
54
+ | The backend request failed or has a CORS error | Wrong backend URL, or your origin is not trusted | Copy the URL from your Inth project exactly, including any path. Add the site's origin, such as `https://www.example.com` or `http://localhost:3000`, to the project's trusted origins. |
55
+ | Requests go to `your-project.inth.app` and fail | The placeholder backend URL from the guides is still in your code | Replace `https://your-project.inth.app` with the URL from your Inth project, including any path prefix. |
56
+ | No backend request at all | No provider mounted, the provider runs in offline mode, or the page resolved from a bundled manifest | Check `window.c15t`. `mode: 'manifest'` with no request is expected for a policy that does not depend on location. Otherwise confirm your framework's backend URL variable is set, as [consent modes](../concepts/modes.md#set-the-backend-url) lists. |
57
+ | The policy resolved and no prompt is needed | The visitor's location maps to a policy without a prompt, such as a US state without a privacy law in the recommended rules | Expected. Test from a location that needs a prompt. Server helpers such as Next.js `resolveConsent` accept a `country` override for testing; remove it before you deploy. |
58
+ | The policy resolved and a choice is stored | A returning visitor | Expected. Clear site data or use a private window. |
59
+ | The banner is in the DOM but invisible or unstyled | c15t's rules are missing: `styles: false` without an imported stylesheet, a Content Security Policy that blocks c15t's `<style>` elements, Tailwind CSS 3's preflight, or a SvelteKit app without `c15tHandle` | See [stylesheets and CSS layers](../customization/stylesheets.md) for how your framework loads c15t's rules. |
60
60
 
61
61
  Do not "fix" a missing banner by granting every category or turning c15t off.
62
62
  While the policy is unresolved, every optional category stays denied. Turning
@@ -67,23 +67,27 @@ the runtime off lets optional scripts load.
67
67
  c15t only controls scripts you register with it. Find every other loader:
68
68
 
69
69
  * A `<script>` tag in your HTML, layout or `_document`.
70
- * A framework plugin, such as `@next/third-parties`, `nuxt-gtag` or a Vercel
71
- or Netlify analytics toggle.
70
+ * A framework package or plugin, such as `@next/third-parties`,
71
+ `@nuxt/scripts`, `nuxt-gtag` or a Vercel or Netlify analytics toggle.
72
72
  * A tag in Google Tag Manager that fires on page view.
73
73
  * An embed, such as a YouTube iframe, rendered without `ConsentGate`.
74
74
 
75
75
  Remove the extra loader and register the vendor through
76
- [integrations](../integrations/overview.md). Then reload with the Network panel
77
- open and cache disabled. The vendor's domain should be absent until you allow
78
- its category.
76
+ [integrations](../integrations/overview.md). To replace a framework package,
77
+ follow [migrate from `@next/third-parties`](https://c15t.com/docs/frameworks/next/scripts#migrate-from-nextthird-parties),
78
+ [migrate from `@nuxt/scripts`](https://c15t.com/docs/frameworks/nuxt/scripts#migrate-from-nuxtscripts)
79
+ or [migrate from `nuxt-gtag`](https://c15t.com/docs/frameworks/nuxt/scripts#migrate-from-nuxt-gtag).
80
+ Then reload with the Network panel open and cache disabled. The vendor's
81
+ domain should be absent until you allow its category.
79
82
 
80
83
  Also check the policy. Under an opt-out policy, optional categories are allowed
81
84
  before a choice, so the request is expected.
82
85
 
83
- Google Tag and Google Tag Manager are an exception by design. Their helpers
84
- load before a choice and send Google Consent Mode signals, so a request to
85
- Google before consent is expected. If you need no request at all before
86
- consent, read [Google Tag Manager](../integrations/google-tag-manager.md) first.
86
+ Google Tag and Google Tag Manager are an exception by design. By default
87
+ their helpers load before a choice and send Google Consent Mode signals, so a
88
+ request to Google before consent is expected. If you need no request at all
89
+ before consent, set `loadMode: 'after-consent'`; see
90
+ [Google Tag Manager](../integrations/google-tag-manager.md#choose-when-the-container-loads).
87
91
 
88
92
  ## Why does the choice disappear on reload?
89
93
 
@@ -116,6 +120,66 @@ Never keep consent state in a module-level variable on the server. One
116
120
  visitor's state can leak into another request. Create it per request, as the
117
121
  framework guides do.
118
122
 
123
+ ## Why does the build fail to download the manifest?
124
+
125
+ `manifest()` resolves from a policy snapshot that the build downloads from
126
+ `${backendURL}/manifest`. A production build stops when the download fails,
127
+ and dev logs the same message as a warning. Every framework uses the same
128
+ messages, prefixed with the integration's name, such as
129
+ `@c15t/nextjs/build`, `@c15t/core/build` (for `c15t/build`),
130
+ `@c15t/tanstack-start/build`, `@c15t/vue/vite`, `@c15t/svelte/vite`,
131
+ `@c15t/vue` (Nuxt) or `@c15t/astro`:
132
+
133
+ | Message | Cause | Fix |
134
+ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
135
+ | `could not fetch the consent manifest from <url> during the build (fetch failed: ...)`, with `ENOTFOUND`, `ECONNREFUSED` or another network error | The build cannot reach the backend | Build where the backend is reachable, or deploy with `C15T_ON_BUILD_ERROR=runtime` |
136
+ | `... (no response within 10 seconds)` | The backend did not answer in time | Check the backend, or build where it is reachable |
137
+ | `... (/manifest responded 404 Not Found)`, or another status | The URL is not your project's backend, such as the `https://your-project.inth.app` placeholder or a URL missing its path prefix | Copy the backend URL exactly as Inth shows it |
138
+ | `... (/manifest returned an invalid consent manifest.)` | The URL answered with something other than a consent manifest, such as an HTML page | Point the backend URL at the backend itself, not your site or a dashboard page |
139
+ | `no backend URL is set, so the build cannot fetch the consent manifest. Pass backendURL or set ...` | No `backendURL` option and no backend URL variable | Set the variable your framework reads; see [consent modes](../concepts/modes.md#set-the-backend-url) |
140
+ | `skipped the consent manifest fetch because "/api/c15t" is not an absolute http(s) URL, so the server fetches the policy at runtime.` | A relative backend URL. A notice, not an error. | Pass the absolute backend URL. With `onBuildError: 'fail'`, this stops the build with `build-time manifests require an absolute upstream URL.` |
141
+ | `C15T_ON_BUILD_ERROR must be 'runtime' or 'fail', received "..."` | The variable has another value | Set it to `runtime` or `fail`, or unset it |
142
+ | `onBuildError must be 'runtime' or 'fail', received "..."` | The option has another value | Pass `'runtime'` or `'fail'` |
143
+
144
+ Each failure message ends with what to do next. In a production build:
145
+ ``Set `C15T_ON_BUILD_ERROR=runtime` (or `onBuildError: 'runtime'`) to deploy with runtime fetching.``
146
+ The Vite plugins add one more fix. The plugin can't see a mode set in app
147
+ code, so an app that uses `manifest({ manifestURL })` or
148
+ `manifest({ source: 'runtime' })` should pass `source: 'runtime'` to
149
+ `consentManifest()`, and the build skips the download.
150
+ A failed download never reuses an older snapshot. With
151
+ `C15T_ON_BUILD_ERROR=runtime`, the build finishes without a snapshot, and the
152
+ manifest is fetched at runtime. Dev warnings end with
153
+ `The server fetches it at runtime instead. A production build stops on this error.`
154
+
155
+ [Consent modes](../concepts/modes.md#what-happens-when-the-download-fails)
156
+ describes the policy and when the build skips the download.
157
+
158
+ ### A single-page app warns that the policy depends on location
159
+
160
+ `consentManifest()` from `c15t/build` logs this warning when it downloads a
161
+ policy with location rules:
162
+
163
+ ```txt
164
+ @c15t/core/build: the consent policy depends on the visitor's location, which the browser doesn't know. Unless you pass `inputs` or `geoURL` to `manifest()`, every first visit still calls the backend's /init, so the bundled policy adds bytes without saving a request. Consider `mode: hosted()` instead.
165
+ ```
166
+
167
+ Pass the visitor's `{ country, region }` as `manifest({ inputs })` when your
168
+ edge knows it, or a `geoURL` that answers with it. Otherwise switch the mode
169
+ to `hosted()`, which asks `/init` without bundling the policy.
170
+
171
+ ## Why does the provider say a mode is required?
172
+
173
+ The browser throws when a provider or `init()` gets no mode, or a mode name
174
+ instead of a factory:
175
+
176
+ | Message | Fix |
177
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
178
+ | ``@c15t/react ConsentProvider: `mode` is required. Use manifest() or hosted().`` | Pass `mode`, such as `manifest()` from the same package. The start of the message names the package and the API that got no mode, such as `@c15t/svelte ConsentProvider` or `@c15t/core createConsentRuntime()`. Production builds name the package only: ``@c15t/react: `mode` is required.`` |
179
+ | ``@c15t/browser: `mode` must be a factory, such as manifest(), hosted() or offline() from @c15t/browser. Mode names like "hosted" work only in the script-tag builds.`` | Import the factory and call it: `init({ mode: hosted() })`. |
180
+ | ``c15t: hosted() needs `backendURL`. Pass it, or add consentManifest() from c15t/build to your Vite config and set VITE_C15T_BACKEND_URL (or VITE_INTH_PROJECT_URL).`` | The React `hosted()` found no backend URL. `@c15t/browser` throws the same message prefixed `@c15t/browser:`. A production build prints only the first sentence. |
181
+ | ``c15t: `mode` is the hosted() transport itself, so its code ships in the client bundle. Pass the hosted() your framework package exports instead: it is plain data, and the root loads the code only when it runs.`` | A warning outside production. A server-rendered root got a transport from `c15t/react`. Import the mode from your framework package. Next.js has its own wording; see [Next.js troubleshooting](https://c15t.com/docs/frameworks/next/troubleshooting). |
182
+
119
183
  ## Why does the static build fail when development works?
120
184
 
121
185
  A static host serves files only. Route handlers, server functions, rewrites
@@ -135,7 +199,7 @@ every optional category stays denied.
135
199
 
136
200
  ## Why does my theme do nothing?
137
201
 
138
- Check that the c15t stylesheet loads, that you set a token or slot the
202
+ Check that c15t's rules load, that you set a token or slot the
139
203
  component actually reads, and that the state attribute you target is on the
140
204
  element you style. A `data-variant` on the banner root is not on its card.
141
205
  Check the Tailwind version and CSS layer order before adding specificity. See
@@ -44,10 +44,11 @@ nothing.
44
44
  | Turn that category off again | The page reloads, and the vendor does not load after the reload. |
45
45
  | Navigate between pages without a full reload | Each vendor loads once, not once per navigation. |
46
46
 
47
- Google Tag and Google Tag Manager load before consent on purpose and send
48
- Consent Mode signals. For them, check that the consent state in the request
49
- parameters changes when the visitor chooses, not that requests are absent.
50
- See [Google Tag Manager](../integrations/google-tag-manager.md).
47
+ By default, Google Tag and Google Tag Manager load before consent on purpose
48
+ and send Consent Mode signals. For them, check that the consent state in the
49
+ request parameters changes when the visitor chooses, not that requests are
50
+ absent. With `loadMode: 'after-consent'`, they follow the table above. See
51
+ [Google Tag Manager](../integrations/google-tag-manager.md).
51
52
 
52
53
  ## Check each location
53
54
 
@@ -72,10 +73,24 @@ having clicked anything. Turn GPC off and confirm the restriction ends.
72
73
 
73
74
  ## Check failure behavior
74
75
 
75
- Block the backend URL in DevTools (right-click the request, then **Block
76
- request URL**) and reload. No banner appears, and no optional vendor loads.
77
- A missing banner must never mean "allowed". Then unblock it and confirm the
78
- page recovers.
76
+ Make the request that initializes consent fail, then reload. Depending on your
77
+ setup, that is the backend `/init`, a same-origin init route, a manifest fetch,
78
+ or the `/init` a bundled manifest falls back to when a regional policy needs a
79
+ location the browser does not know.
80
+
81
+ * For a request the browser makes, block it in DevTools (right-click the
82
+ request, then **Block request URL**).
83
+ * When the server resolves consent before the page loads, the browser never
84
+ sees that request. Make it fail on the server instead, for example by
85
+ pointing the backend URL at an unreachable address for one run.
86
+ * With a custom transport, make its `init` fail.
87
+
88
+ No banner appears, and vendors that wait for consent send no requests. Skip this
89
+ check only if the page makes no initialization request at all, such as a
90
+ build-time manifest with no regional fallback. A helper that always loads, such
91
+ as `gtag` with its default `loadMode`, still loads; check that it reports denied
92
+ consent. A missing banner must never mean "allowed". Then undo the change and
93
+ confirm the page recovers.
79
94
 
80
95
  ## Check the interface
81
96
 
@@ -3,22 +3,32 @@ title: Adobe Analytics
3
3
  description: Load an Adobe Data Collection Tags property only after measurement
4
4
  consent with the c15t adobeAnalytics helper, and check its extensions in
5
5
  DevTools.
6
+ seoTitle: Adobe Analytics cookie consent with c15t
7
+ seoDescription: Load Adobe Analytics only with measurement consent. The c15t
8
+ adobeAnalytics helper gates your Adobe Data Collection Tags property.
6
9
  icon: adobe-analytics
7
10
  group: integrations
8
11
  ---
9
12
 
13
+ The c15t `adobeAnalytics` helper keeps your Adobe Data Collection Tags property
14
+ off the page until `measurement` has consent. This property loads Adobe
15
+ Analytics. The helper sends no consent signal to Adobe, so every extension in
16
+ the property runs under the consent for `measurement`. If the visitor withdraws
17
+ consent, c15t removes the script element. By default, c15t also reloads the
18
+ page.
19
+
10
20
  ## Configure Adobe Analytics
11
21
 
12
22
  Copy the full embed URL for your property environment from the Adobe Data
13
23
  Collection embed code. Adobe has no shared loader that takes only an account
14
24
  ID, so `adobeAnalytics` needs the whole URL. Remove the original embed code.
15
25
 
16
- | Package manager | Command |
17
- | :-------------- | :------------------------------------- |
18
- | npm | `npm install @c15t/integrations@alpha` |
19
- | pnpm | `pnpm add @c15t/integrations@alpha` |
20
- | yarn | `yarn add @c15t/integrations@alpha` |
21
- | bun | `bun add @c15t/integrations@alpha` |
26
+ | Package manager | Command |
27
+ | :-------------- | :------------------------------- |
28
+ | npm | `npm install @c15t/integrations` |
29
+ | pnpm | `pnpm add @c15t/integrations` |
30
+ | yarn | `yarn add @c15t/integrations` |
31
+ | bun | `bun add @c15t/integrations` |
22
32
 
23
33
  ```ts title="src/consent-scripts.ts"
24
34
  import { adobeAnalytics } from '@c15t/integrations/adobe-analytics';
@@ -38,51 +48,41 @@ endpoint, policy, styles and consent UI. Remove the vendor's original script,
38
48
  SDK initializer or tag-manager entry, so the vendor loads only through c15t.
39
49
 
40
50
  The vendor pages put the helper in `src/consent-scripts.ts`. If your framework
41
- quickstart already created a scripts file, such as `lib/scripts.ts` in the
42
- Next.js guide, add the helper to that array instead of creating a second file.
51
+ quickstart already has a `scripts` array, such as the one in `c15t.config.ts`
52
+ in the Next.js guide, add the helper to that array instead of creating a
53
+ second file.
43
54
  The `scripts` export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration
44
55
  point for your framework below. These are edits to that provider, not a second
45
56
  provider.
46
57
 
47
58
  **Next.js**
48
59
 
49
- Import the configuration into the client boundary from your router guide:
50
-
51
- ```ts
52
- import { ConsentRoot } from 'c15t/next';
53
- import { scripts } from './consent-scripts';
54
- ```
60
+ Add the configuration to `scripts` in `c15t.config.ts`, next to
61
+ `next.config.ts`:
55
62
 
56
- Keep the server-resolved `state` and shared `consentConfig` from your
57
- router guide. Its manifest, init and save URLs stay in effect. Add
58
- `scripts` as a top-level prop on the existing root:
63
+ ```ts title="c15t.config.ts"
64
+ import { defineConsentConfig } from 'c15t/next';
65
+ import { scripts } from './src/consent-scripts';
59
66
 
60
- ```tsx
61
- <ConsentRoot state={state} config={consentConfig} scripts={scripts}>
62
- {children}
63
- </ConsentRoot>
67
+ export default defineConsentConfig({ scripts });
64
68
  ```
65
69
 
66
- App Router, Pages Router and static export all use this `ConsentRoot` in
67
- the `'use client'` wrapper `components/consent.tsx`. Keep `scripts` there,
68
- because a Server Component cannot pass script callbacks to it. See
70
+ Keep the rest of your config, such as `mode` and `routePrefix`, in the
71
+ same call. `ConsentRoot` reads the config in the browser, so the layout
72
+ keeps passing only `state`. App Router, Pages Router and static export all
73
+ read the same file. See
69
74
  [Next.js scripts and embeds](https://c15t.com/docs/frameworks/next/scripts).
70
75
 
71
76
  **TanStack Start**
72
77
 
73
78
  Import the configuration into your root route and pass it to the existing
74
- `ConsentRoot` as a top-level prop. Keep the loader, `backendURL` and
75
- `initRoute` from the [TanStack Start quickstart](https://c15t.com/docs/frameworks/tanstack-start/quickstart):
79
+ `ConsentRoot` as a top-level prop. Keep the loader from the
80
+ [TanStack Start quickstart](https://c15t.com/docs/frameworks/tanstack-start/quickstart):
76
81
 
77
82
  ```tsx title="src/routes/__root.tsx"
78
83
  import { scripts } from '../consent-scripts';
79
84
 
80
- <ConsentRoot
81
- state={consent}
82
- backendURL={backendURL}
83
- initRoute={false}
84
- scripts={scripts}
85
- >
85
+ <ConsentRoot state={consent} scripts={scripts}>
86
86
  ```
87
87
 
88
88
  Import vendor helpers in the root route module, not in a server function.
@@ -101,8 +101,8 @@ import { scripts } from './consent-scripts';
101
101
  <ConsentProvider options={{ mode, scripts }}>
102
102
  ```
103
103
 
104
- `mode` is the `hosted({ url: 'https://your-project.inth.app' })` value
105
- from the [React quickstart](../frameworks/react/quickstart.md). Keep the banner,
104
+ `mode` is the `manifest()` value from the
105
+ [React quickstart](../frameworks/react/quickstart.md). Keep the banner,
106
106
  dialog and preferences link inside the provider. See
107
107
  [React scripts and embeds](../frameworks/react/scripts.md).
108
108
 
@@ -131,15 +131,16 @@ callbacks. Write the vendor IDs into `consent-scripts.ts`. See
131
131
  Pass the scripts to the existing `c15tVue` plugin call in `src/main.ts`:
132
132
 
133
133
  ```ts title="src/main.ts"
134
+ import { c15tVue, manifest } from 'c15t/vue/vue-plugin';
134
135
  import { scripts } from './consent-scripts';
135
136
 
136
137
  app.use(c15tVue, {
137
- backendURL: 'https://your-project.inth.app',
138
+ mode: manifest(),
138
139
  scripts,
139
140
  });
140
141
  ```
141
142
 
142
- Keep your existing backend URL and other options. The plugin starts one
143
+ Keep your existing `mode` and other options. The plugin starts one
143
144
  script loader when the app mounts, after it has applied the visitor's stored
144
145
  choice. Do not also call `createScriptLoader` from a component. See
145
146
  [Vue scripts and embeds](https://c15t.com/docs/frameworks/vue/scripts).
@@ -147,12 +148,11 @@ choice. Do not also call `createScriptLoader` from a component. See
147
148
  **Astro**
148
149
 
149
150
  Add the scripts to the client entrypoint from the
150
- [Astro quickstart](https://c15t.com/docs/frameworks/astro/quickstart), the module that the
151
- integration's `clientEntrypoint` option names. Keep `mode`, `ui` and the
152
- framework integration in `astro.config.mjs` as they are. If the module
153
- already exports scripts, combine the two arrays.
151
+ [Astro quickstart](https://c15t.com/docs/frameworks/astro/quickstart), `src/c15t.client.ts`,
152
+ which the integration finds on its own. Keep `astro.config.mjs` as it is.
153
+ If the module already exports scripts, combine the two arrays.
154
154
 
155
- ```ts title="src/consent-client.ts"
155
+ ```ts title="src/c15t.client.ts"
156
156
  import type { C15tClientOptionsExtension } from 'c15t/astro';
157
157
  import { scripts } from './consent-scripts';
158
158
 
@@ -172,15 +172,13 @@ pass them as a top-level prop:
172
172
 
173
173
  ```svelte title="src/App.svelte"
174
174
  <script lang="ts">
175
- import { ConsentManagerProvider, hosted } from '@c15t/svelte';
175
+ import { ConsentProvider, manifest } from '@c15t/svelte';
176
176
  import { scripts } from './consent-scripts';
177
-
178
- const mode = hosted({ url: 'https://your-project.inth.app' });
179
177
  </script>
180
178
 
181
- <ConsentManagerProvider {mode} {scripts}>
179
+ <ConsentProvider mode={manifest()} {scripts}>
182
180
  <!-- Keep your application, consent UI and preferences link here. -->
183
- </ConsentManagerProvider>
181
+ </ConsentProvider>
184
182
  ```
185
183
 
186
184
  Retain the styles and consent UI from the [Svelte quickstart](https://c15t.com/docs/frameworks/svelte/quickstart).
@@ -188,22 +186,21 @@ The provider owns the loader and disposes it on unmount.
188
186
 
189
187
  **SvelteKit**
190
188
 
191
- Add the scripts to the existing root layout provider. Keep the server load
192
- and its serializable prefetch data from the [SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart).
189
+ Add the scripts to the existing `ConsentRoot` in the root layout. Keep the
190
+ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart).
193
191
 
194
192
  ```svelte title="src/routes/+layout.svelte"
195
193
  <script lang="ts">
196
- import { ConsentManagerProvider, hosted } from '@c15t/svelte';
194
+ import { ConsentRoot } from '@c15t/svelte';
197
195
  import { scripts } from '../consent-scripts';
198
196
 
199
197
  let { children, data } = $props();
200
- const mode = hosted({ url: 'https://your-project.inth.app' });
201
198
  </script>
202
199
 
203
- <ConsentManagerProvider {mode} {scripts} prefetch={data.prefetch}>
200
+ <ConsentRoot state={data.consent} {scripts}>
204
201
  {@render children()}
205
202
  <!-- Keep your consent UI and preferences link here. -->
206
- </ConsentManagerProvider>
203
+ </ConsentRoot>
207
204
  ```
208
205
 
209
206
  Import vendor helpers in the layout component, not in `+layout.server.ts`:
@@ -230,20 +227,19 @@ to a pasted snippet. See [HTML scripts](https://c15t.com/docs/frameworks/html/sc
230
227
 
231
228
  **JavaScript**
232
229
 
233
- Pass the scripts to `init()` from `@c15t/browser`, next to your backend
234
- URL:
230
+ Pass the scripts to `init()` from `@c15t/browser`, next to your mode:
235
231
 
236
232
  ```ts
237
- import { init } from '@c15t/browser';
233
+ import { init, manifest } from '@c15t/browser';
238
234
  import { scripts } from './consent-scripts';
239
235
 
240
236
  const consent = init({
241
- backendURL: 'https://your-project.inth.app',
237
+ mode: manifest(),
242
238
  scripts,
243
239
  });
244
240
  ```
245
241
 
246
- Keep the backend URL from your quickstart. With
242
+ Keep the mode from your quickstart. With
247
243
  `createConsentRuntime` from `c15t/runtime`, pass `scripts` to it instead.
248
244
  A kernel you create yourself needs a loader from
249
245
  `c15t/modules/script-loader`. Attach one loader per kernel. See
@@ -251,11 +247,11 @@ A kernel you create yourself needs a loader from
251
247
 
252
248
  ## Options
253
249
 
254
- | Option | Default | Behavior |
255
- | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
256
- | `scriptUrl` | Required | Full `https:` embed URL of the Tags property environment. Self-hosted embeds work. The helper trims the value. Empty, unparseable and non-`https:` URLs throw. |
257
- | `async` | `true` | Loads the Tags library asynchronously. Set `false` only for a legacy synchronous deployment. |
258
- | `seedAdobeDataLayer` | `true` | Sets `window.adobeDataLayer` to an empty array before the library loads, only when the global is undefined. |
250
+ | Option | Default | Behavior |
251
+ | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
252
+ | `scriptUrl` | Required | Full `https:` embed URL of the Tags property environment. Self-hosted embeds work. The helper trims the value. Empty, unparseable and non-`https:` URLs log an error and the script does not load. |
253
+ | `async` | `true` | Loads the Tags library asynchronously. Set `false` only for a legacy synchronous deployment. |
254
+ | `seedAdobeDataLayer` | `true` | Sets `window.adobeDataLayer` to an empty array before the library loads, only when the global is undefined. |
259
255
 
260
256
  ## Loading and revocation
261
257
 
@@ -295,8 +291,8 @@ permission before each of your own event calls. The reload does not delete
295
291
  cookies the vendor already set; see
296
292
  [clear on revocation for your framework](./overview.md#vendor-switches-and-cookie-cleanup).
297
293
 
298
- The helper sets `vendor` to its script ID, so once you declare that vendor a
299
- visitor can turn it off inside an allowed category. See
294
+ The helper lists its vendor by name in the preference dialog, so a visitor can
295
+ turn it off inside an allowed category. See
300
296
  [vendor consent for your framework](./overview.md#vendor-switches-and-cookie-cleanup). The
301
297
  [consent verification guide](../guides/verify-consent.md) covers navigation,
302
298
  expiry and hosting checks.