@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
@@ -1,23 +1,39 @@
1
1
  ---
2
2
  title: Google Tag
3
- description: Load gtag.js for Google Analytics or Google Ads with c15t Consent
4
- Mode v2 signals, and verify the consent commands in DevTools.
3
+ description: Load gtag.js for Google Analytics or Google Ads before or after
4
+ consent with c15t Consent Mode v2 signals, and verify the consent commands in
5
+ DevTools.
6
+ seoTitle: Google Tag Consent Mode v2 with c15t
7
+ seoDescription: Send Google Consent Mode v2 signals before gtag.js runs. c15t
8
+ maps consent to analytics_storage, ad_storage, ad_user_data and
9
+ ad_personalization.
5
10
  icon: google-analytics
6
11
  group: integrations
7
12
  ---
8
13
 
14
+ The c15t `gtag` helper loads Google's gtag.js for Google Analytics or Google
15
+ Ads. With the default `loadMode: 'always'`, gtag.js loads on every page, before
16
+ the visitor chooses. You must give a category, usually `measurement` or
17
+ `marketing`, but in this mode the category does not delay the load. With
18
+ `loadMode: 'after-consent'`, c15t sends nothing to Google until the category has
19
+ consent. In both modes, the helper sends the Google Consent Mode v2 default with
20
+ `gtag('consent', 'default', ...)` before `config`. Each consent change sends an
21
+ `update`. `measurement` controls `analytics_storage`, and `marketing` controls
22
+ `ad_storage`, `ad_user_data`, and `ad_personalization`. The `consentMapping`
23
+ option replaces this map.
24
+
9
25
  ## Configure the Google tag
10
26
 
11
27
  Copy the tag ID, for example `G-XXXXXXXXXX` for Google Analytics or
12
28
  `AW-XXXXXXXXX` for Google Ads. Remove any `gtag.js` snippet already in your
13
29
  HTML and any tag-manager entry that loads the same tag.
14
30
 
15
- | Package manager | Command |
16
- | :-------------- | :------------------------------------- |
17
- | npm | `npm install @c15t/integrations@alpha` |
18
- | pnpm | `pnpm add @c15t/integrations@alpha` |
19
- | yarn | `yarn add @c15t/integrations@alpha` |
20
- | bun | `bun add @c15t/integrations@alpha` |
31
+ | Package manager | Command |
32
+ | :-------------- | :------------------------------- |
33
+ | npm | `npm install @c15t/integrations` |
34
+ | pnpm | `pnpm add @c15t/integrations` |
35
+ | yarn | `yarn add @c15t/integrations` |
36
+ | bun | `bun add @c15t/integrations` |
21
37
 
22
38
  ```ts title="src/consent-scripts.ts"
23
39
  import { gtag } from '@c15t/integrations/google-tag';
@@ -32,51 +48,41 @@ endpoint, policy, styles and consent UI. Remove the vendor's original script,
32
48
  SDK initializer or tag-manager entry, so the vendor loads only through c15t.
33
49
 
34
50
  The vendor pages put the helper in `src/consent-scripts.ts`. If your framework
35
- quickstart already created a scripts file, such as `lib/scripts.ts` in the
36
- 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.
37
54
  The `scripts` export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration
38
55
  point for your framework below. These are edits to that provider, not a second
39
56
  provider.
40
57
 
41
58
  **Next.js**
42
59
 
43
- Import the configuration into the client boundary from your router guide:
44
-
45
- ```ts
46
- import { ConsentRoot } from 'c15t/next';
47
- import { scripts } from './consent-scripts';
48
- ```
60
+ Add the configuration to `scripts` in `c15t.config.ts`, next to
61
+ `next.config.ts`:
49
62
 
50
- Keep the server-resolved `state` and shared `consentConfig` from your
51
- router guide. Its manifest, init and save URLs stay in effect. Add
52
- `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';
53
66
 
54
- ```tsx
55
- <ConsentRoot state={state} config={consentConfig} scripts={scripts}>
56
- {children}
57
- </ConsentRoot>
67
+ export default defineConsentConfig({ scripts });
58
68
  ```
59
69
 
60
- App Router, Pages Router and static export all use this `ConsentRoot` in
61
- the `'use client'` wrapper `components/consent.tsx`. Keep `scripts` there,
62
- 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
63
74
  [Next.js scripts and embeds](https://c15t.com/docs/frameworks/next/scripts).
64
75
 
65
76
  **TanStack Start**
66
77
 
67
78
  Import the configuration into your root route and pass it to the existing
68
- `ConsentRoot` as a top-level prop. Keep the loader, `backendURL` and
69
- `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):
70
81
 
71
82
  ```tsx title="src/routes/__root.tsx"
72
83
  import { scripts } from '../consent-scripts';
73
84
 
74
- <ConsentRoot
75
- state={consent}
76
- backendURL={backendURL}
77
- initRoute={false}
78
- scripts={scripts}
79
- >
85
+ <ConsentRoot state={consent} scripts={scripts}>
80
86
  ```
81
87
 
82
88
  Import vendor helpers in the root route module, not in a server function.
@@ -95,8 +101,8 @@ import { scripts } from './consent-scripts';
95
101
  <ConsentProvider options={{ mode, scripts }}>
96
102
  ```
97
103
 
98
- `mode` is the `hosted({ url: 'https://your-project.inth.app' })` value
99
- 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,
100
106
  dialog and preferences link inside the provider. See
101
107
  [React scripts and embeds](../frameworks/react/scripts.md).
102
108
 
@@ -125,15 +131,16 @@ callbacks. Write the vendor IDs into `consent-scripts.ts`. See
125
131
  Pass the scripts to the existing `c15tVue` plugin call in `src/main.ts`:
126
132
 
127
133
  ```ts title="src/main.ts"
134
+ import { c15tVue, manifest } from 'c15t/vue/vue-plugin';
128
135
  import { scripts } from './consent-scripts';
129
136
 
130
137
  app.use(c15tVue, {
131
- backendURL: 'https://your-project.inth.app',
138
+ mode: manifest(),
132
139
  scripts,
133
140
  });
134
141
  ```
135
142
 
136
- Keep your existing backend URL and other options. The plugin starts one
143
+ Keep your existing `mode` and other options. The plugin starts one
137
144
  script loader when the app mounts, after it has applied the visitor's stored
138
145
  choice. Do not also call `createScriptLoader` from a component. See
139
146
  [Vue scripts and embeds](https://c15t.com/docs/frameworks/vue/scripts).
@@ -141,12 +148,11 @@ choice. Do not also call `createScriptLoader` from a component. See
141
148
  **Astro**
142
149
 
143
150
  Add the scripts to the client entrypoint from the
144
- [Astro quickstart](https://c15t.com/docs/frameworks/astro/quickstart), the module that the
145
- integration's `clientEntrypoint` option names. Keep `mode`, `ui` and the
146
- framework integration in `astro.config.mjs` as they are. If the module
147
- 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.
148
154
 
149
- ```ts title="src/consent-client.ts"
155
+ ```ts title="src/c15t.client.ts"
150
156
  import type { C15tClientOptionsExtension } from 'c15t/astro';
151
157
  import { scripts } from './consent-scripts';
152
158
 
@@ -166,15 +172,13 @@ pass them as a top-level prop:
166
172
 
167
173
  ```svelte title="src/App.svelte"
168
174
  <script lang="ts">
169
- import { ConsentManagerProvider, hosted } from '@c15t/svelte';
175
+ import { ConsentProvider, manifest } from '@c15t/svelte';
170
176
  import { scripts } from './consent-scripts';
171
-
172
- const mode = hosted({ url: 'https://your-project.inth.app' });
173
177
  </script>
174
178
 
175
- <ConsentManagerProvider {mode} {scripts}>
179
+ <ConsentProvider mode={manifest()} {scripts}>
176
180
  <!-- Keep your application, consent UI and preferences link here. -->
177
- </ConsentManagerProvider>
181
+ </ConsentProvider>
178
182
  ```
179
183
 
180
184
  Retain the styles and consent UI from the [Svelte quickstart](https://c15t.com/docs/frameworks/svelte/quickstart).
@@ -182,22 +186,21 @@ The provider owns the loader and disposes it on unmount.
182
186
 
183
187
  **SvelteKit**
184
188
 
185
- Add the scripts to the existing root layout provider. Keep the server load
186
- 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).
187
191
 
188
192
  ```svelte title="src/routes/+layout.svelte"
189
193
  <script lang="ts">
190
- import { ConsentManagerProvider, hosted } from '@c15t/svelte';
194
+ import { ConsentRoot } from '@c15t/svelte';
191
195
  import { scripts } from '../consent-scripts';
192
196
 
193
197
  let { children, data } = $props();
194
- const mode = hosted({ url: 'https://your-project.inth.app' });
195
198
  </script>
196
199
 
197
- <ConsentManagerProvider {mode} {scripts} prefetch={data.prefetch}>
200
+ <ConsentRoot state={data.consent} {scripts}>
198
201
  {@render children()}
199
202
  <!-- Keep your consent UI and preferences link here. -->
200
- </ConsentManagerProvider>
203
+ </ConsentRoot>
201
204
  ```
202
205
 
203
206
  Import vendor helpers in the layout component, not in `+layout.server.ts`:
@@ -224,20 +227,19 @@ to a pasted snippet. See [HTML scripts](https://c15t.com/docs/frameworks/html/sc
224
227
 
225
228
  **JavaScript**
226
229
 
227
- Pass the scripts to `init()` from `@c15t/browser`, next to your backend
228
- URL:
230
+ Pass the scripts to `init()` from `@c15t/browser`, next to your mode:
229
231
 
230
232
  ```ts
231
- import { init } from '@c15t/browser';
233
+ import { init, manifest } from '@c15t/browser';
232
234
  import { scripts } from './consent-scripts';
233
235
 
234
236
  const consent = init({
235
- backendURL: 'https://your-project.inth.app',
237
+ mode: manifest(),
236
238
  scripts,
237
239
  });
238
240
  ```
239
241
 
240
- Keep the backend URL from your quickstart. With
242
+ Keep the mode from your quickstart. With
241
243
  `createConsentRuntime` from `c15t/runtime`, pass `scripts` to it instead.
242
244
  A kernel you create yourself needs a loader from
243
245
  `c15t/modules/script-loader`. Attach one loader per kernel. See
@@ -245,28 +247,69 @@ A kernel you create yourself needs a loader from
245
247
 
246
248
  ## Options
247
249
 
248
- | Option | Default | Behavior |
249
- | ---------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
250
- | `id` | Required | Tag ID passed to `gtag('config', ...)` and the loader URL. The helper trims it. Empty or whitespace-only values throw. |
251
- | `category` | Required | `measurement` for Analytics, `marketing` for Ads and Floodlight. It sets the script's permission, which callbacks receive, but does not delay loading. |
252
- | `config` | None | Parameters passed as the third argument to `gtag('config', id, config)`. |
253
- | `consentMapping` | The table below | Replaces the category-to-Google mapping. |
250
+ | Option | Default | Behavior |
251
+ | ---------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
252
+ | `id` | Required | Tag ID passed to `gtag('config', ...)` and the loader URL. The helper trims it. Empty or whitespace-only values log an error and the script does not load. |
253
+ | `category` | Required | `measurement` for Analytics, `marketing` for Ads and Floodlight. With `loadMode: 'after-consent'`, `gtag/js` waits for it. With `'always'`, it sets the script's permission, which callbacks receive, but does not delay loading. |
254
+ | `loadMode` | `'always'` | When `gtag/js` loads. See [choose when Google loads](#choose-when-google-loads). |
255
+ | `config` | None | Parameters passed as the third argument to `gtag('config', id, config)`. |
256
+ | `consentMapping` | The table below | Replaces the category-to-Google mapping. |
254
257
 
255
258
  The deprecated `script` option overrides fields of the returned script. Use the
256
259
  options above instead.
257
260
 
258
- ## Google loads before a choice
261
+ ## Choose when Google loads
262
+
263
+ | `loadMode` | Until the category is allowed | After the category is allowed |
264
+ | ----------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
265
+ | `'always'` | Loads `gtag/js` and sends `consent` `default` with the current permissions, then `config`. | Sends `consent` `update` with the mapped types granted. |
266
+ | `'after-consent'` | Sends nothing to Google. The helper creates no `dataLayer` or `gtag` function. | Loads `gtag/js` once. `consent` `default` carries the current permissions and comes before `config`. Later changes send `update`. |
259
267
 
260
- The `googleTagManager` and `gtag` helpers set `alwaysLoad: true`. Before the
261
- visitor chooses, the helper creates the `dataLayer` queue, sends
262
- `gtag('consent', 'default', ...)` with the current permissions and loads
263
- Google's script. After each permission change it sends
268
+ Use `'after-consent'` when your policy forbids any request to Google before
269
+ the visitor opts in:
270
+
271
+ ```ts title="src/consent-scripts.ts (partial)"
272
+ gtag({ id: 'G-XXXXXXXXXX', category: 'measurement', loadMode: 'after-consent' }),
273
+ ```
274
+
275
+ `'after-consent'` waits for the category to be allowed, not for a recorded
276
+ choice. Under an `opt-in` policy, that happens when the visitor allows it.
277
+ Under an `opt-out` or `none` policy, optional categories are allowed before a
278
+ choice, so `gtag/js` loads on the first page unless a saved refusal or a
279
+ privacy signal restricts the category; see
280
+ [policies](../concepts/policies.md). `necessary` is always allowed, so
281
+ `category: 'necessary'` loads `gtag/js` before a choice in either mode. Use
282
+ `measurement` or `marketing`.
283
+
284
+ This gives up part of Consent Mode. With `'always'`, Google tags send
285
+ cookieless pings while a type is denied, and Google uses them to model
286
+ conversions and behavior for visitors who refused or have not chosen. With
287
+ `'after-consent'`, visitors whose category is denied send nothing, so Google
288
+ has no data to model them from. Reports cover only visitors who allowed the
289
+ category.
290
+
291
+ When the visitor withdraws the category, c15t reloads the page and the new
292
+ page does not load `gtag/js`. With `reloadOnConsentRevoked: false`, the tag
293
+ already running stays on the page and gets an `update` that denies the
294
+ withdrawn types. A later grant reuses it instead of loading a second copy.
295
+
296
+ ## Google loads before a choice by default
297
+
298
+ With the default `loadMode: 'always'`, the `googleTagManager` and `gtag`
299
+ helpers set `alwaysLoad: true`. Before the visitor chooses, the helper creates
300
+ the `dataLayer` queue, sends `gtag('consent', 'default', ...)` with the current
301
+ permissions and loads Google's script. After each permission change it sends
264
302
  `gtag('consent', 'update', ...)`. Google's tags then adjust what they store and
265
303
  send; see Google's
266
304
  [Consent Mode overview](https://developers.google.com/tag-platform/security/concepts/consent-mode).
267
305
 
268
- So the browser does contact Google before consent. If your policy requires no
269
- Google request until the visitor allows it, do not use these helpers unchanged.
306
+ So the browser contacts Google before consent. If your policy requires no
307
+ Google request until the visitor allows it, set `loadMode: 'after-consent'`.
308
+ The helper then creates nothing and requests nothing until its category is
309
+ allowed. When it loads, it sends the `default` command first, with the
310
+ permissions at that moment, and `update` commands after later changes. Under
311
+ an `opt-out` or `none` policy, optional categories are allowed before a
312
+ choice, so the helper loads on the first page.
270
313
 
271
314
  ## How categories map to Google consent types
272
315
 
@@ -288,11 +331,12 @@ visitor has chosen anything. That is a permission, not a recorded choice.
288
331
 
289
332
  ## Verify the Google tag
290
333
 
291
- These checks are for the `gtag` helper, which loads before a choice on
292
- purpose. On a plain HTML page with the script tag, you gate Google's snippet
293
- instead and it loads only after consent; see
334
+ On a plain HTML page with the script tag, you gate Google's snippet instead
335
+ and it loads only after consent; see
294
336
  [HTML scripts](https://c15t.com/docs/frameworks/html/scripts#google-consent-mode-and-tag-managers).
295
337
 
338
+ With the default `loadMode: 'always'`:
339
+
296
340
  1. In a private window with an opt-in policy, load the page. `gtag/js` loads.
297
341
  In Google Tag Assistant, the `consent` `default` command comes before
298
342
  `config`, with `analytics_storage` and `ad_storage` set to `denied`.
@@ -300,9 +344,21 @@ instead and it loads only after consent; see
300
344
  `update` command that grants the mapped types.
301
345
  3. Turn the category off again and save. c15t reloads the page, and the new
302
346
  page starts with those types denied.
303
- 4. With client-side navigation, check that each route change sends one page
304
- view. A separate router integration that also sends `page_view` doubles
305
- the count.
347
+
348
+ With `loadMode: 'after-consent'`:
349
+
350
+ 1. In a private window with an opt-in policy, filter DevTools Network by
351
+ `google` and load the page. No request appears, and `window.dataLayer` is
352
+ `undefined` in the Console.
353
+ 2. Allow the tag's category. `gtag/js` loads once. In Tag Assistant, the first
354
+ command is `consent` `default` with the mapped types granted, before
355
+ `config`.
356
+ 3. Turn the category off again and save. c15t reloads the page, and the new
357
+ page makes no request to Google.
358
+
359
+ In either mode, with client-side navigation, check that each route change
360
+ sends one page view. A separate router integration that also sends
361
+ `page_view` doubles the count.
306
362
 
307
363
  See the [consent verification guide](../guides/verify-consent.md) for hosting
308
364
  checks.
@@ -2,22 +2,32 @@
2
2
  title: Heap
3
3
  description: Load the Heap config script and heap.js only after measurement
4
4
  consent with the c15t heap helper, and check it in DevTools.
5
+ seoTitle: Heap cookie consent with c15t
6
+ seoDescription: Load Heap only with measurement consent. The c15t heap helper
7
+ keeps heap_config.js off the page and stops Heap when a visitor withdraws
8
+ consent.
5
9
  icon: heap
6
10
  group: integrations
7
11
  ---
8
12
 
13
+ The c15t `heap` helper keeps Heap off the page until `measurement` has consent.
14
+ Until then, c15t does not define `window.heap` or load `heap_config.js`, which
15
+ loads the heap.js runtime. The helper never calls `heap.stopTracking`. If the
16
+ visitor withdraws consent, c15t removes the config script element. By default,
17
+ c15t also reloads the page. The reload stops heap.js.
18
+
9
19
  ## Configure Heap
10
20
 
11
21
  Copy the environment ID from your Heap installation snippet. The `heap` helper
12
22
  loads that environment's `heap_config.js`, which then loads the heap.js
13
23
  runtime. Remove the original Heap snippet.
14
24
 
15
- | Package manager | Command |
16
- | :-------------- | :------------------------------------- |
17
- | npm | `npm install @c15t/integrations@alpha` |
18
- | pnpm | `pnpm add @c15t/integrations@alpha` |
19
- | yarn | `yarn add @c15t/integrations@alpha` |
20
- | bun | `bun add @c15t/integrations@alpha` |
25
+ | Package manager | Command |
26
+ | :-------------- | :------------------------------- |
27
+ | npm | `npm install @c15t/integrations` |
28
+ | pnpm | `pnpm add @c15t/integrations` |
29
+ | yarn | `yarn add @c15t/integrations` |
30
+ | bun | `bun add @c15t/integrations` |
21
31
 
22
32
  ```ts title="src/consent-scripts.ts"
23
33
  import { heap } from '@c15t/integrations/heap';
@@ -32,51 +42,41 @@ endpoint, policy, styles and consent UI. Remove the vendor's original script,
32
42
  SDK initializer or tag-manager entry, so the vendor loads only through c15t.
33
43
 
34
44
  The vendor pages put the helper in `src/consent-scripts.ts`. If your framework
35
- quickstart already created a scripts file, such as `lib/scripts.ts` in the
36
- Next.js guide, add the helper to that array instead of creating a second file.
45
+ quickstart already has a `scripts` array, such as the one in `c15t.config.ts`
46
+ in the Next.js guide, add the helper to that array instead of creating a
47
+ second file.
37
48
  The `scripts` export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration
38
49
  point for your framework below. These are edits to that provider, not a second
39
50
  provider.
40
51
 
41
52
  **Next.js**
42
53
 
43
- Import the configuration into the client boundary from your router guide:
44
-
45
- ```ts
46
- import { ConsentRoot } from 'c15t/next';
47
- import { scripts } from './consent-scripts';
48
- ```
54
+ Add the configuration to `scripts` in `c15t.config.ts`, next to
55
+ `next.config.ts`:
49
56
 
50
- Keep the server-resolved `state` and shared `consentConfig` from your
51
- router guide. Its manifest, init and save URLs stay in effect. Add
52
- `scripts` as a top-level prop on the existing root:
57
+ ```ts title="c15t.config.ts"
58
+ import { defineConsentConfig } from 'c15t/next';
59
+ import { scripts } from './src/consent-scripts';
53
60
 
54
- ```tsx
55
- <ConsentRoot state={state} config={consentConfig} scripts={scripts}>
56
- {children}
57
- </ConsentRoot>
61
+ export default defineConsentConfig({ scripts });
58
62
  ```
59
63
 
60
- App Router, Pages Router and static export all use this `ConsentRoot` in
61
- the `'use client'` wrapper `components/consent.tsx`. Keep `scripts` there,
62
- because a Server Component cannot pass script callbacks to it. See
64
+ Keep the rest of your config, such as `mode` and `routePrefix`, in the
65
+ same call. `ConsentRoot` reads the config in the browser, so the layout
66
+ keeps passing only `state`. App Router, Pages Router and static export all
67
+ read the same file. See
63
68
  [Next.js scripts and embeds](https://c15t.com/docs/frameworks/next/scripts).
64
69
 
65
70
  **TanStack Start**
66
71
 
67
72
  Import the configuration into your root route and pass it to the existing
68
- `ConsentRoot` as a top-level prop. Keep the loader, `backendURL` and
69
- `initRoute` from the [TanStack Start quickstart](https://c15t.com/docs/frameworks/tanstack-start/quickstart):
73
+ `ConsentRoot` as a top-level prop. Keep the loader from the
74
+ [TanStack Start quickstart](https://c15t.com/docs/frameworks/tanstack-start/quickstart):
70
75
 
71
76
  ```tsx title="src/routes/__root.tsx"
72
77
  import { scripts } from '../consent-scripts';
73
78
 
74
- <ConsentRoot
75
- state={consent}
76
- backendURL={backendURL}
77
- initRoute={false}
78
- scripts={scripts}
79
- >
79
+ <ConsentRoot state={consent} scripts={scripts}>
80
80
  ```
81
81
 
82
82
  Import vendor helpers in the root route module, not in a server function.
@@ -95,8 +95,8 @@ import { scripts } from './consent-scripts';
95
95
  <ConsentProvider options={{ mode, scripts }}>
96
96
  ```
97
97
 
98
- `mode` is the `hosted({ url: 'https://your-project.inth.app' })` value
99
- from the [React quickstart](../frameworks/react/quickstart.md). Keep the banner,
98
+ `mode` is the `manifest()` value from the
99
+ [React quickstart](../frameworks/react/quickstart.md). Keep the banner,
100
100
  dialog and preferences link inside the provider. See
101
101
  [React scripts and embeds](../frameworks/react/scripts.md).
102
102
 
@@ -125,15 +125,16 @@ callbacks. Write the vendor IDs into `consent-scripts.ts`. See
125
125
  Pass the scripts to the existing `c15tVue` plugin call in `src/main.ts`:
126
126
 
127
127
  ```ts title="src/main.ts"
128
+ import { c15tVue, manifest } from 'c15t/vue/vue-plugin';
128
129
  import { scripts } from './consent-scripts';
129
130
 
130
131
  app.use(c15tVue, {
131
- backendURL: 'https://your-project.inth.app',
132
+ mode: manifest(),
132
133
  scripts,
133
134
  });
134
135
  ```
135
136
 
136
- Keep your existing backend URL and other options. The plugin starts one
137
+ Keep your existing `mode` and other options. The plugin starts one
137
138
  script loader when the app mounts, after it has applied the visitor's stored
138
139
  choice. Do not also call `createScriptLoader` from a component. See
139
140
  [Vue scripts and embeds](https://c15t.com/docs/frameworks/vue/scripts).
@@ -141,12 +142,11 @@ choice. Do not also call `createScriptLoader` from a component. See
141
142
  **Astro**
142
143
 
143
144
  Add the scripts to the client entrypoint from the
144
- [Astro quickstart](https://c15t.com/docs/frameworks/astro/quickstart), the module that the
145
- integration's `clientEntrypoint` option names. Keep `mode`, `ui` and the
146
- framework integration in `astro.config.mjs` as they are. If the module
147
- already exports scripts, combine the two arrays.
145
+ [Astro quickstart](https://c15t.com/docs/frameworks/astro/quickstart), `src/c15t.client.ts`,
146
+ which the integration finds on its own. Keep `astro.config.mjs` as it is.
147
+ If the module already exports scripts, combine the two arrays.
148
148
 
149
- ```ts title="src/consent-client.ts"
149
+ ```ts title="src/c15t.client.ts"
150
150
  import type { C15tClientOptionsExtension } from 'c15t/astro';
151
151
  import { scripts } from './consent-scripts';
152
152
 
@@ -166,15 +166,13 @@ pass them as a top-level prop:
166
166
 
167
167
  ```svelte title="src/App.svelte"
168
168
  <script lang="ts">
169
- import { ConsentManagerProvider, hosted } from '@c15t/svelte';
169
+ import { ConsentProvider, manifest } from '@c15t/svelte';
170
170
  import { scripts } from './consent-scripts';
171
-
172
- const mode = hosted({ url: 'https://your-project.inth.app' });
173
171
  </script>
174
172
 
175
- <ConsentManagerProvider {mode} {scripts}>
173
+ <ConsentProvider mode={manifest()} {scripts}>
176
174
  <!-- Keep your application, consent UI and preferences link here. -->
177
- </ConsentManagerProvider>
175
+ </ConsentProvider>
178
176
  ```
179
177
 
180
178
  Retain the styles and consent UI from the [Svelte quickstart](https://c15t.com/docs/frameworks/svelte/quickstart).
@@ -182,22 +180,21 @@ The provider owns the loader and disposes it on unmount.
182
180
 
183
181
  **SvelteKit**
184
182
 
185
- Add the scripts to the existing root layout provider. Keep the server load
186
- and its serializable prefetch data from the [SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart).
183
+ Add the scripts to the existing `ConsentRoot` in the root layout. Keep the
184
+ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart).
187
185
 
188
186
  ```svelte title="src/routes/+layout.svelte"
189
187
  <script lang="ts">
190
- import { ConsentManagerProvider, hosted } from '@c15t/svelte';
188
+ import { ConsentRoot } from '@c15t/svelte';
191
189
  import { scripts } from '../consent-scripts';
192
190
 
193
191
  let { children, data } = $props();
194
- const mode = hosted({ url: 'https://your-project.inth.app' });
195
192
  </script>
196
193
 
197
- <ConsentManagerProvider {mode} {scripts} prefetch={data.prefetch}>
194
+ <ConsentRoot state={data.consent} {scripts}>
198
195
  {@render children()}
199
196
  <!-- Keep your consent UI and preferences link here. -->
200
- </ConsentManagerProvider>
197
+ </ConsentRoot>
201
198
  ```
202
199
 
203
200
  Import vendor helpers in the layout component, not in `+layout.server.ts`:
@@ -224,20 +221,19 @@ to a pasted snippet. See [HTML scripts](https://c15t.com/docs/frameworks/html/sc
224
221
 
225
222
  **JavaScript**
226
223
 
227
- Pass the scripts to `init()` from `@c15t/browser`, next to your backend
228
- URL:
224
+ Pass the scripts to `init()` from `@c15t/browser`, next to your mode:
229
225
 
230
226
  ```ts
231
- import { init } from '@c15t/browser';
227
+ import { init, manifest } from '@c15t/browser';
232
228
  import { scripts } from './consent-scripts';
233
229
 
234
230
  const consent = init({
235
- backendURL: 'https://your-project.inth.app',
231
+ mode: manifest(),
236
232
  scripts,
237
233
  });
238
234
  ```
239
235
 
240
- Keep the backend URL from your quickstart. With
236
+ Keep the mode from your quickstart. With
241
237
  `createConsentRuntime` from `c15t/runtime`, pass `scripts` to it instead.
242
238
  A kernel you create yourself needs a loader from
243
239
  `c15t/modules/script-loader`. Attach one loader per kernel. See
@@ -247,7 +243,7 @@ A kernel you create yourself needs a loader from
247
243
 
248
244
  | Option | Default | Behavior |
249
245
  | -------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
250
- | `envId` | Required | Environment ID. Set as `heap.envId` and `heap.appid`, and used in the default loader URL. The helper trims it. Empty values throw. |
246
+ | `envId` | Required | Environment ID. Set as `heap.envId` and `heap.appid`, and used in the default loader URL. The helper trims it. Empty values log an error and the script does not load. |
251
247
  | `clientConfig` | `{}` | Client configuration set on `heap.clientConfig`. Values must be JSON-serializable with finite numbers; other values throw a `TypeError`. The helper always sets `shouldFetchServerConfig: false` and overrides any value you pass for it. |
252
248
  | `scriptUrl` | `https://cdn.us.heap-api.com/config/<envId>/heap_config.js` | Config loader URL override. A blank value falls back to the default. |
253
249
 
@@ -293,8 +289,8 @@ permission before each of your own event calls. The reload does not delete
293
289
  cookies the vendor already set; see
294
290
  [clear on revocation for your framework](./overview.md#vendor-switches-and-cookie-cleanup).
295
291
 
296
- The helper sets `vendor` to its script ID, so once you declare that vendor a
297
- visitor can turn it off inside an allowed category. See
292
+ The helper lists its vendor by name in the preference dialog, so a visitor can
293
+ turn it off inside an allowed category. See
298
294
  [vendor consent for your framework](./overview.md#vendor-switches-and-cookie-cleanup). The
299
295
  [consent verification guide](../guides/verify-consent.md) covers navigation,
300
296
  expiry and hosting checks.