@c15t/scripts 3.0.0-alpha.2 → 3.0.0-alpha.4

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 (300) hide show
  1. package/AGENTS.md +129 -63
  2. package/README.md +8 -29
  3. package/SKILL.md +33 -0
  4. package/dist/adobe-analytics.js +2 -0
  5. package/dist/ahrefs-analytics.js +2 -0
  6. package/dist/amplitude.js +2 -0
  7. package/dist/clearbit.js +2 -0
  8. package/dist/cloudflare-web-analytics.js +2 -0
  9. package/dist/cloudflare-zaraz.js +2 -0
  10. package/dist/crisp.js +2 -0
  11. package/dist/databuddy.js +2 -0
  12. package/dist/e2e-test-utils.js +2 -139
  13. package/dist/engine/compile.js +2 -89
  14. package/dist/engine/runtime.js +2 -448
  15. package/dist/events.js +2 -218
  16. package/dist/fathom-analytics.js +2 -0
  17. package/dist/front-chat.js +2 -0
  18. package/dist/google-tag-manager.js +2 -0
  19. package/dist/google-tag.js +2 -0
  20. package/dist/heap.js +2 -0
  21. package/dist/hightouch.js +2 -0
  22. package/dist/hotjar.js +2 -0
  23. package/dist/intercom.js +2 -0
  24. package/dist/klaviyo.js +2 -0
  25. package/dist/linkedin-insights.js +2 -0
  26. package/dist/logrocket.js +2 -0
  27. package/dist/matomo-analytics.js +2 -0
  28. package/dist/meta-pixel.js +2 -0
  29. package/dist/microsoft-clarity.js +2 -0
  30. package/dist/microsoft-uet.js +2 -0
  31. package/dist/mixpanel-analytics.js +2 -0
  32. package/dist/one-dollar-stats.js +2 -0
  33. package/dist/openai-pixel.js +2 -0
  34. package/dist/pinterest-tag.js +2 -0
  35. package/dist/pirsch.js +2 -0
  36. package/dist/plausible-analytics.js +2 -0
  37. package/dist/posthog.js +2 -0
  38. package/dist/promptwatch.js +2 -0
  39. package/dist/reddit-pixel.js +2 -0
  40. package/dist/registry.js +2 -422
  41. package/dist/resolve.js +2 -33
  42. package/dist/rudderstack.js +2 -0
  43. package/dist/rybbit-analytics.js +2 -0
  44. package/dist/segment.js +2 -0
  45. package/dist/snapchat-pixel.js +2 -0
  46. package/dist/tiktok-pixel.js +2 -0
  47. package/dist/types.js +2 -16
  48. package/dist/umami-analytics.js +2 -0
  49. package/dist/vendors/_shared/attributes.js +2 -14
  50. package/dist/vendors/_shared/google-consent.js +2 -27
  51. package/dist/vendors/_shared/install-builders.js +2 -21
  52. package/dist/vendors/_shared/required-id.js +2 -0
  53. package/dist/vendors/_shared/script-url.js +2 -28
  54. package/dist/vendors/ads-and-pixels/linkedin-insights.js +2 -48
  55. package/dist/vendors/ads-and-pixels/meta-pixel.js +2 -153
  56. package/dist/vendors/ads-and-pixels/microsoft-uet.js +2 -110
  57. package/dist/vendors/ads-and-pixels/openai-pixel.js +2 -88
  58. package/dist/vendors/ads-and-pixels/pinterest-tag.js +2 -123
  59. package/dist/vendors/ads-and-pixels/reddit-pixel.js +2 -107
  60. package/dist/vendors/ads-and-pixels/snapchat-pixel.js +2 -87
  61. package/dist/vendors/ads-and-pixels/tiktok-pixel.js +2 -89
  62. package/dist/vendors/ads-and-pixels/x-pixel.js +2 -48
  63. package/dist/vendors/analytics/adobe-analytics.js +2 -49
  64. package/dist/vendors/analytics/ahrefs-analytics.js +2 -27
  65. package/dist/vendors/analytics/amplitude.js +2 -134
  66. package/dist/vendors/analytics/clearbit.js +2 -28
  67. package/dist/vendors/analytics/cloudflare-web-analytics.js +2 -32
  68. package/dist/vendors/analytics/databuddy.js +2 -103
  69. package/dist/vendors/analytics/fathom-analytics.js +2 -35
  70. package/dist/vendors/analytics/google-tag.js +2 -78
  71. package/dist/vendors/analytics/heap.js +2 -134
  72. package/dist/vendors/analytics/hightouch.js +2 -109
  73. package/dist/vendors/analytics/hotjar.js +2 -44
  74. package/dist/vendors/analytics/logrocket.js +2 -58
  75. package/dist/vendors/analytics/matomo-analytics.js +2 -191
  76. package/dist/vendors/analytics/microsoft-clarity.js +2 -100
  77. package/dist/vendors/analytics/mixpanel-analytics.js +2 -93
  78. package/dist/vendors/analytics/one-dollar-stats.js +2 -30
  79. package/dist/vendors/analytics/pirsch.js +2 -67
  80. package/dist/vendors/analytics/plausible-analytics.js +2 -81
  81. package/dist/vendors/analytics/posthog.js +2 -200
  82. package/dist/vendors/analytics/promptwatch.js +2 -29
  83. package/dist/vendors/analytics/rudderstack.js +2 -183
  84. package/dist/vendors/analytics/rybbit-analytics.js +2 -63
  85. package/dist/vendors/analytics/segment.js +2 -65
  86. package/dist/vendors/analytics/umami-analytics.js +2 -39
  87. package/dist/vendors/analytics/vercel-analytics.js +2 -53
  88. package/dist/vendors/email-and-sms/klaviyo.js +2 -0
  89. package/dist/vendors/functional/crisp.js +2 -100
  90. package/dist/vendors/functional/front-chat.js +2 -64
  91. package/dist/vendors/functional/intercom.js +2 -45
  92. package/dist/vendors/tag-managers/cloudflare-zaraz.js +2 -98
  93. package/dist/vendors/tag-managers/google-tag-manager.js +2 -73
  94. package/dist/vercel-analytics.js +2 -0
  95. package/dist/x-pixel.js +2 -0
  96. package/dist-types/adobe-analytics.d.ts +2 -0
  97. package/dist-types/ahrefs-analytics.d.ts +2 -0
  98. package/dist-types/amplitude.d.ts +2 -0
  99. package/dist-types/clearbit.d.ts +2 -0
  100. package/dist-types/cloudflare-web-analytics.d.ts +2 -0
  101. package/dist-types/cloudflare-zaraz.d.ts +2 -0
  102. package/dist-types/crisp.d.ts +2 -0
  103. package/dist-types/databuddy.d.ts +2 -0
  104. package/dist-types/e2e-test-utils.d.ts +2 -0
  105. package/dist-types/engine/compile.d.ts +2 -3
  106. package/dist-types/engine/runtime.d.ts +2 -3
  107. package/dist-types/events.d.ts +2 -46
  108. package/dist-types/fathom-analytics.d.ts +2 -0
  109. package/dist-types/front-chat.d.ts +2 -0
  110. package/dist-types/google-tag-manager.d.ts +2 -0
  111. package/dist-types/google-tag.d.ts +2 -0
  112. package/dist-types/heap.d.ts +2 -0
  113. package/dist-types/hightouch.d.ts +2 -0
  114. package/dist-types/hotjar.d.ts +2 -0
  115. package/dist-types/intercom.d.ts +2 -0
  116. package/dist-types/klaviyo.d.ts +2 -0
  117. package/dist-types/linkedin-insights.d.ts +2 -0
  118. package/dist-types/logrocket.d.ts +2 -0
  119. package/dist-types/matomo-analytics.d.ts +2 -0
  120. package/dist-types/meta-pixel.d.ts +2 -0
  121. package/dist-types/microsoft-clarity.d.ts +2 -0
  122. package/dist-types/microsoft-uet.d.ts +2 -0
  123. package/dist-types/mixpanel-analytics.d.ts +2 -0
  124. package/dist-types/one-dollar-stats.d.ts +2 -0
  125. package/dist-types/openai-pixel.d.ts +2 -0
  126. package/dist-types/pinterest-tag.d.ts +2 -0
  127. package/dist-types/pirsch.d.ts +2 -0
  128. package/dist-types/plausible-analytics.d.ts +2 -0
  129. package/dist-types/posthog.d.ts +2 -0
  130. package/dist-types/promptwatch.d.ts +2 -0
  131. package/dist-types/reddit-pixel.d.ts +2 -0
  132. package/dist-types/registry.d.ts +2 -485
  133. package/dist-types/resolve.d.ts +2 -9
  134. package/dist-types/rudderstack.d.ts +2 -0
  135. package/dist-types/rybbit-analytics.d.ts +2 -0
  136. package/dist-types/segment.d.ts +2 -0
  137. package/dist-types/snapchat-pixel.d.ts +2 -0
  138. package/dist-types/tiktok-pixel.d.ts +2 -0
  139. package/dist-types/types.d.ts +2 -314
  140. package/dist-types/umami-analytics.d.ts +2 -0
  141. package/dist-types/vendors/_shared/attributes.d.ts +2 -35
  142. package/dist-types/vendors/_shared/google-consent.d.ts +2 -47
  143. package/dist-types/vendors/_shared/install-builders.d.ts +2 -30
  144. package/dist-types/vendors/_shared/required-id.d.ts +2 -0
  145. package/dist-types/vendors/_shared/script-url.d.ts +2 -75
  146. package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +2 -92
  147. package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +2 -289
  148. package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +2 -105
  149. package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +2 -211
  150. package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +2 -295
  151. package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +2 -210
  152. package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +2 -171
  153. package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +2 -106
  154. package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +2 -183
  155. package/dist-types/vendors/analytics/adobe-analytics.d.ts +2 -75
  156. package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +2 -62
  157. package/dist-types/vendors/analytics/amplitude.d.ts +2 -234
  158. package/dist-types/vendors/analytics/clearbit.d.ts +2 -60
  159. package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +2 -67
  160. package/dist-types/vendors/analytics/databuddy.d.ts +2 -147
  161. package/dist-types/vendors/analytics/fathom-analytics.d.ts +2 -90
  162. package/dist-types/vendors/analytics/google-tag.d.ts +2 -95
  163. package/dist-types/vendors/analytics/heap.d.ts +2 -316
  164. package/dist-types/vendors/analytics/hightouch.d.ts +2 -285
  165. package/dist-types/vendors/analytics/hotjar.d.ts +2 -73
  166. package/dist-types/vendors/analytics/logrocket.d.ts +2 -101
  167. package/dist-types/vendors/analytics/matomo-analytics.d.ts +2 -41
  168. package/dist-types/vendors/analytics/microsoft-clarity.d.ts +2 -97
  169. package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +2 -113
  170. package/dist-types/vendors/analytics/one-dollar-stats.d.ts +2 -39
  171. package/dist-types/vendors/analytics/pirsch.d.ts +2 -96
  172. package/dist-types/vendors/analytics/plausible-analytics.d.ts +2 -122
  173. package/dist-types/vendors/analytics/posthog.d.ts +2 -175
  174. package/dist-types/vendors/analytics/promptwatch.d.ts +2 -36
  175. package/dist-types/vendors/analytics/rudderstack.d.ts +2 -330
  176. package/dist-types/vendors/analytics/rybbit-analytics.d.ts +2 -82
  177. package/dist-types/vendors/analytics/segment.d.ts +2 -164
  178. package/dist-types/vendors/analytics/umami-analytics.d.ts +2 -93
  179. package/dist-types/vendors/analytics/vercel-analytics.d.ts +2 -66
  180. package/dist-types/vendors/email-and-sms/klaviyo.d.ts +2 -0
  181. package/dist-types/vendors/functional/crisp.d.ts +2 -78
  182. package/dist-types/vendors/functional/front-chat.d.ts +2 -62
  183. package/dist-types/vendors/functional/intercom.d.ts +2 -135
  184. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +2 -39
  185. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +2 -96
  186. package/dist-types/vercel-analytics.d.ts +2 -0
  187. package/dist-types/x-pixel.d.ts +2 -0
  188. package/docs/README.md +129 -63
  189. package/docs/assets/v3/bottom-bar.png +0 -0
  190. package/docs/assets/v3/brand-card.png +0 -0
  191. package/docs/assets/v3/brand-preferences.png +0 -0
  192. package/docs/assets/v3/choice-wall.png +0 -0
  193. package/docs/assets/v3/headless-bar-html.png +0 -0
  194. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  195. package/docs/assets/v3/headless-bar.png +0 -0
  196. package/docs/assets/v3/slim-bar.png +0 -0
  197. package/docs/concepts/choose-your-setup.md +87 -0
  198. package/docs/concepts/consent-categories.md +84 -0
  199. package/docs/{guides → concepts}/consent-state.md +89 -105
  200. package/docs/{guides → concepts}/data-fetching.md +31 -27
  201. package/docs/concepts/how-consent-works.md +123 -0
  202. package/docs/concepts/policies.md +71 -0
  203. package/docs/customization/class-names.md +202 -0
  204. package/docs/customization/dark-mode.md +157 -0
  205. package/docs/customization/motion.md +119 -0
  206. package/docs/customization/overview.md +67 -34
  207. package/docs/customization/recipes.md +839 -49
  208. package/docs/customization/slots.md +216 -35
  209. package/docs/customization/stylesheets.md +147 -0
  210. package/docs/customization/tailwind.md +842 -0
  211. package/docs/customization/tokens.md +163 -96
  212. package/docs/customization/translations.md +60 -3
  213. package/docs/frameworks/astro/embeds.md +160 -0
  214. package/docs/frameworks/astro/network-blocker.md +86 -0
  215. package/docs/frameworks/astro/scripts.md +155 -0
  216. package/docs/frameworks/html/embeds.md +142 -0
  217. package/docs/frameworks/html/network-blocker.md +105 -0
  218. package/docs/frameworks/html/scripts.md +164 -0
  219. package/docs/frameworks/javascript/scripts.md +137 -0
  220. package/docs/frameworks/next/embeds.md +90 -0
  221. package/docs/frameworks/next/network-blocker.md +153 -0
  222. package/docs/frameworks/next/scripts.md +196 -0
  223. package/docs/frameworks/nuxt/embeds.md +81 -0
  224. package/docs/frameworks/nuxt/network-blocker.md +97 -0
  225. package/docs/frameworks/nuxt/scripts.md +89 -0
  226. package/docs/frameworks/react/embeds.md +89 -0
  227. package/docs/frameworks/react/network-blocker.md +140 -0
  228. package/docs/frameworks/react/scripts.md +115 -0
  229. package/docs/frameworks/svelte/embeds.md +96 -0
  230. package/docs/frameworks/svelte/network-blocker.md +141 -0
  231. package/docs/frameworks/svelte/scripts.md +144 -0
  232. package/docs/frameworks/sveltekit/embeds.md +103 -0
  233. package/docs/frameworks/sveltekit/network-blocker.md +159 -0
  234. package/docs/frameworks/sveltekit/scripts.md +172 -0
  235. package/docs/frameworks/tanstack-start/embeds.md +96 -0
  236. package/docs/frameworks/tanstack-start/network-blocker.md +145 -0
  237. package/docs/frameworks/tanstack-start/scripts.md +103 -0
  238. package/docs/frameworks/vue/embeds.md +84 -0
  239. package/docs/frameworks/vue/network-blocker.md +99 -0
  240. package/docs/frameworks/vue/scripts.md +93 -0
  241. package/docs/guides/banner-experiments.md +654 -0
  242. package/docs/guides/troubleshooting.md +120 -47
  243. package/docs/guides/verify-consent.md +81 -49
  244. package/docs/integrations/adobe-analytics.md +167 -159
  245. package/docs/integrations/ahrefs-analytics.md +152 -154
  246. package/docs/integrations/amplitude.md +162 -156
  247. package/docs/integrations/building-integrations.md +136 -37
  248. package/docs/integrations/clearbit.md +154 -154
  249. package/docs/integrations/cloudflare-web-analytics.md +156 -156
  250. package/docs/integrations/cloudflare-zaraz.md +209 -261
  251. package/docs/integrations/crisp.md +164 -158
  252. package/docs/integrations/databuddy.md +157 -173
  253. package/docs/integrations/fathom-analytics.md +158 -156
  254. package/docs/integrations/front-chat.md +167 -167
  255. package/docs/integrations/google-maps.md +118 -83
  256. package/docs/integrations/google-tag-manager.md +178 -163
  257. package/docs/integrations/google-tag.md +163 -160
  258. package/docs/integrations/heap.md +163 -155
  259. package/docs/integrations/hightouch.md +161 -157
  260. package/docs/integrations/hotjar.md +159 -155
  261. package/docs/integrations/intercom.md +183 -153
  262. package/docs/integrations/klaviyo.md +486 -0
  263. package/docs/integrations/linkedin-insights.md +174 -150
  264. package/docs/integrations/logrocket.md +160 -156
  265. package/docs/integrations/matomo-analytics.md +188 -178
  266. package/docs/integrations/meta-pixel.md +188 -150
  267. package/docs/integrations/microsoft-clarity.md +163 -155
  268. package/docs/integrations/microsoft-uet.md +148 -154
  269. package/docs/integrations/mixpanel-analytics.md +155 -160
  270. package/docs/integrations/one-dollar-stats.md +166 -165
  271. package/docs/integrations/openai-pixel.md +204 -301
  272. package/docs/integrations/overview.md +137 -80
  273. package/docs/integrations/pinterest-tag.md +191 -183
  274. package/docs/integrations/pirsch.md +169 -159
  275. package/docs/integrations/plausible-analytics.md +172 -158
  276. package/docs/integrations/posthog.md +313 -240
  277. package/docs/integrations/promptwatch.md +154 -154
  278. package/docs/integrations/reddit-pixel.md +185 -157
  279. package/docs/integrations/rudderstack.md +201 -186
  280. package/docs/integrations/rybbit-analytics.md +171 -160
  281. package/docs/integrations/segment.md +182 -154
  282. package/docs/integrations/snapchat-pixel.md +186 -156
  283. package/docs/integrations/tiktok-pixel.md +171 -150
  284. package/docs/integrations/umami-analytics.md +163 -158
  285. package/docs/integrations/vercel-analytics.md +166 -157
  286. package/docs/integrations/x-pixel.md +176 -150
  287. package/docs/integrations/youtube.md +121 -86
  288. package/docs/upgrade-v3.md +496 -467
  289. package/package.json +10 -257
  290. package/dist-types/__tests__/helpers.d.ts +0 -141
  291. package/docs/assets/v3/brand-bar.png +0 -0
  292. package/docs/assets/v3/mobile-card.png +0 -0
  293. package/docs/assets/v3/preferences.png +0 -0
  294. package/docs/frameworks/javascript/script-loader.md +0 -100
  295. package/docs/frameworks/next/script-loader.md +0 -216
  296. package/docs/frameworks/react/script-loader.md +0 -69
  297. package/docs/guides/deployment-modes.md +0 -75
  298. package/docs/guides/shared-consent-controls.md +0 -158
  299. package/docs/integrations/clear-on-revocation.md +0 -167
  300. package/docs/integrations/granular-consent.md +0 -210
@@ -0,0 +1,137 @@
1
+ ---
2
+ title: Scripts
3
+ description: Register vendor scripts with @c15t/browser, createConsentRuntime or
4
+ a consent kernel in JavaScript, and control what happens when a visitor
5
+ withdraws permission.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Register scripts where you start c15t
10
+
11
+ Pass the `scripts` list to whichever API starts c15t. The list is the same in
12
+ each case, built from `@c15t/integrations` helpers or your own script objects:
13
+
14
+ | Setup | Where `scripts` goes |
15
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `@c15t/browser` | `init({ backendURL, scripts })`, as in the [quickstart](https://c15t.com/docs/frameworks/javascript/quickstart#start-c15t). |
17
+ | `c15t/runtime` | `createConsentRuntime({ mode, scripts })`, as in [headless](https://c15t.com/docs/frameworks/javascript/headless#create-the-runtime). |
18
+ | Your own kernel | `createScriptLoader({ kernel, scripts })`. See [script loader](https://c15t.com/docs/frameworks/javascript/modules/script-loader#attach-the-loader-to-your-own-kernel). |
19
+
20
+ The quickstart's `src/scripts.ts` shows PostHog and X Pixel:
21
+
22
+ ```ts title="src/scripts.ts"
23
+ import { posthog } from '@c15t/integrations/posthog';
24
+ import { xPixel } from '@c15t/integrations/x-pixel';
25
+
26
+ export const scripts = [
27
+ posthog({
28
+ id: 'phc_your_project_key',
29
+ initOptions: { cookieless_mode: 'never' },
30
+ loadMode: 'after-consent',
31
+ }),
32
+ xPixel({ pixelId: 'your-pixel-id' }),
33
+ ];
34
+ ```
35
+
36
+ Each [integration guide](../../integrations/overview.md) gives the helper and
37
+ options for one vendor. For a vendor without a helper, write the object
38
+ yourself:
39
+
40
+ ```ts
41
+ const scripts = [
42
+ {
43
+ id: 'analytics',
44
+ src: 'https://analytics.example/sdk.js',
45
+ category: 'measurement',
46
+ onLoad: () => window.analytics?.track('page_view'),
47
+ },
48
+ ];
49
+ ```
50
+
51
+ `window.analytics` stands for your vendor's global. The
52
+ [script loader](https://c15t.com/docs/frameworks/javascript/modules/script-loader) page lists
53
+ every field and callback.
54
+
55
+ ## When the script loader downloads
56
+
57
+ With `@c15t/browser` from npm, the script loader and the network blocker
58
+ share a separate chunk. It loads when c15t starts, and only if `scripts` is
59
+ not empty or `networkBlocker` has rules, so a page with neither never
60
+ downloads it. On a page with either, the browser requests it after your
61
+ JavaScript has run, which delays a returning visitor's consented scripts and
62
+ held requests by one request.
63
+
64
+ Your bundler names that chunk, so c15t cannot link it from your HTML. To
65
+ start the download earlier, add a `<link rel="modulepreload">` for the chunk
66
+ your build emits for `@c15t/core/dist/modules/loader-and-blocker.js`. With
67
+ Vite, `.vite/manifest.json` lists it under that path when `build.manifest` is
68
+ on. Give the link `fetchpriority="low"`. c15t needs the chunk only when it
69
+ starts, and at the default priority the preload can delay your app's own
70
+ chunks over HTTP/1.1. The script-tag build, `c15t.js`, already contains the
71
+ loader.
72
+
73
+ ## Gate a snippet in your HTML
74
+
75
+ `@c15t/browser` also runs `<script type="text/plain" data-c15t-category>`
76
+ tags in your `index.html` once their category is allowed, in page order. Use
77
+ it for a vendor snippet you would rather keep in HTML. `createConsentRuntime`
78
+ and a plain kernel do not scan the page for these tags;
79
+ `activateGatedScripts(snapshot)` from `@c15t/browser` runs them for you.
80
+
81
+ With the `nonce` option set, `@c15t/browser` runs only the tags that carry the
82
+ same `nonce`, and marks the others `data-c15t-activated="untrusted"`. Pass
83
+ `{ nonce }` as the third argument of `activateGatedScripts` for the same
84
+ check. See [Content Security Policy](https://c15t.com/docs/frameworks/javascript/content-security-policy).
85
+
86
+ ## When a visitor withdraws permission
87
+
88
+ A script that has run cannot be unloaded. `@c15t/browser` and
89
+ `createConsentRuntime` reload the page after a visitor turns off a category
90
+ they had allowed, so the new page starts without that vendor. Allowing a
91
+ category never reloads.
92
+
93
+ Set `reloadOnConsentRevoked: false` to handle withdrawal yourself, for
94
+ example with the vendor's opt-out call in the script's `onConsentChange`.
95
+ Until the next page load, code that already ran keeps running.
96
+ `callbacks.onBeforeConsentRevocationReload` runs right before the reload, for
97
+ work that must finish first. A kernel you create yourself does not reload;
98
+ add `watchRevocationReload` from `c15t` if you want it.
99
+
100
+ ## Keep one owner per vendor
101
+
102
+ Use stable script IDs and remove the vendor's original snippet, so each
103
+ vendor loads once and only through c15t. Ordinary scripts wait for
104
+ permission. Helpers with `alwaysLoad` load at once and pass consent to the
105
+ vendor's own API instead. Read the individual integration guide before
106
+ assuming all helpers have the same network behavior.
107
+
108
+ Create `init()` or the runtime once per page. A second client, or a second
109
+ loader on a kernel that `@c15t/browser` or `createConsentRuntime` owns, loads
110
+ vendors twice.
111
+
112
+ ## Let visitors turn off one vendor
113
+
114
+ A visitor can allow marketing and still switch off one vendor in it. Declare
115
+ the vendors in the `vendors` option and render the switch yourself; helpers
116
+ from `@c15t/integrations` already carry their vendor slug. See
117
+ [vendor consent](https://c15t.com/docs/frameworks/javascript/vendor-consent).
118
+
119
+ ## Clear stored tracking data
120
+
121
+ Script gating does not remove cookies or Web Storage entries that a script
122
+ already wrote. Configure [clear on revocation](https://c15t.com/docs/frameworks/javascript/clear-on-revocation)
123
+ to delete them when their category is denied.
124
+
125
+ ## Send your own events to allowed vendors
126
+
127
+ To send your own analytics events only to the vendors a visitor allows, use
128
+ the event dispatcher from `@c15t/integrations`. See
129
+ [send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations).
130
+
131
+ ## Check it works
132
+
133
+ Run the app in a private window with the Network tab open.
134
+
135
+ 1. Filter for each vendor's host. Nothing loads before a choice.
136
+ 2. Allow one category. Only that category's vendors load.
137
+ 3. Withdraw it. The page reloads and the vendor stays absent.
@@ -0,0 +1,90 @@
1
+ ---
2
+ title: Embeds
3
+ description: Keep YouTube videos, maps and other iframes out of a Next.js page
4
+ until their consent category is allowed, with ConsentGate or the iframe
5
+ blocker in ConsentRoot.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Pick a method
10
+
11
+ | Method | Use it when |
12
+ | ------------------ | -------------------------------------------------------------------------------------------------------- |
13
+ | `ConsentGate` | You render the iframe in a React component and want a placeholder until the visitor allows its category. |
14
+ | The iframe blocker | The iframe comes from markup you do not render with a component, such as CMS or Markdown content. |
15
+
16
+ Both keep the iframe's `src` out of the page until the category is allowed, so
17
+ the embed's host receives no request before consent.
18
+
19
+ ## Gate an embed with ConsentGate
20
+
21
+ Wrap the iframe in `ConsentGate` in a Client Component:
22
+
23
+ ```tsx title="components/video-embed.tsx"
24
+ 'use client';
25
+
26
+ import { ConsentGate } from 'c15t/next';
27
+
28
+ export const VideoEmbed = () => (
29
+ <ConsentGate category="measurement">
30
+ <iframe
31
+ className="video-frame"
32
+ sandbox="allow-scripts allow-same-origin allow-presentation"
33
+ src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y"
34
+ title="YouTube video"
35
+ allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
36
+ allowFullScreen
37
+ />
38
+ </ConsentGate>
39
+ );
40
+ ```
41
+
42
+ Import `VideoEmbed` into any page or Server Component under the layout that
43
+ mounts `ConsentRoot`; only this file needs `'use client'`. The iframe stays out
44
+ of the page until the visitor allows measurement, and a placeholder with a
45
+ button that opens the preference dialog takes its place.
46
+
47
+ Pick the category that matches what the embed does. The example uses
48
+ measurement for YouTube because its player measures views. A map or chat
49
+ widget usually belongs under functionality or experience.
50
+
51
+ What the server HTML contains depends on how your layout passes consent state.
52
+ [ConsentGate](https://c15t.com/docs/frameworks/next/components/consent-gate) covers the
53
+ default, awaited and Pages Router layouts, the placeholder and its props.
54
+
55
+ ## Gate an iframe with the iframe blocker
56
+
57
+ `ConsentRoot` runs the iframe blocker in the browser by default. Give an
58
+ iframe `data-src` instead of `src`, and a `data-category`:
59
+
60
+ ```html title="Markup from your CMS"
61
+ <iframe
62
+ data-src="https://www.google.com/maps/embed?pb=YOUR_EMBED_ID"
63
+ data-category="functionality"
64
+ title="Store map"
65
+ ></iframe>
66
+ ```
67
+
68
+ The server HTML has no `src`, so nothing loads before hydration. After
69
+ hydration, c15t sets `src` from `data-src` when the category is allowed and
70
+ removes `src` when the category is withdrawn. It watches the page, so iframes
71
+ added by client-side navigation are handled too. Add `data-vendor` with a
72
+ vendor ID to also block the iframe while the visitor has turned that vendor
73
+ off.
74
+
75
+ To turn the blocker off, pass `options={{ iframeBlocker: false }}` to
76
+ `ConsentRoot`.
77
+
78
+ ## Verify the embeds
79
+
80
+ Open the production build in a private window with DevTools open, under a
81
+ policy that asks for consent.
82
+
83
+ 1. View the page source. No iframe has a `src` pointing at
84
+ `youtube-nocookie.com` or `google.com/maps`, and the Network panel shows no
85
+ request to them.
86
+ 2. Allow the embed's category and save. The iframe loads.
87
+ 3. Withdraw the category and save. The page reloads without the embed.
88
+
89
+ The [YouTube](../../integrations/youtube.md) and
90
+ [Google Maps](../../integrations/google-maps.md) guides cover sizing and titles.
@@ -0,0 +1,153 @@
1
+ ---
2
+ title: Network blocker
3
+ description: Hold fetch and XMLHttpRequest calls in a Next.js app until their
4
+ consent category is allowed, with networkBlocker rules on ConsentRoot.
5
+ group: frameworks
6
+ ---
7
+
8
+ ## Add rules
9
+
10
+ The network blocker stops `fetch` and `XMLHttpRequest` calls to domains you
11
+ list until the visitor grants their category. Use it for beacons and API calls
12
+ that bypass script loading, such as a pixel fired by an SDK already on the
13
+ page. Load vendor SDKs through [`scripts`](./scripts.md)
14
+ first, so they do not run at all before consent.
15
+
16
+ Define the rules in `components/consent.tsx` and pass them to `ConsentRoot`.
17
+ `onRequestBlocked` is a function, so the rules cannot come from a Server
18
+ Component.
19
+
20
+ ```tsx title="components/consent.tsx (partial)"
21
+ import type { ConsentRootProps } from 'c15t/next';
22
+
23
+ const networkBlocker: NonNullable<ConsentRootProps['networkBlocker']> = {
24
+ rules: [
25
+ {
26
+ id: 'google-analytics',
27
+ domain: 'google-analytics.com',
28
+ category: 'measurement',
29
+ },
30
+ {
31
+ id: 'meta-pixel',
32
+ domain: 'facebook.com',
33
+ pathIncludes: '/tr',
34
+ category: 'marketing',
35
+ },
36
+ ],
37
+ };
38
+
39
+ // On the existing root:
40
+ <ConsentRoot
41
+ state={state}
42
+ config={consentConfig}
43
+ scripts={scripts}
44
+ networkBlocker={networkBlocker}
45
+ >
46
+ ```
47
+
48
+ Keep the page content inside `ConsentRoot`. Blocking starts when it renders, so
49
+ components outside it that send requests while rendering, and modules evaluated
50
+ before it, are not covered. Requests your server makes, in Server Components,
51
+ route handlers or `getServerSideProps`, are not blocked either.
52
+
53
+ ## Match requests with rules
54
+
55
+ Each rule names a `domain` and the consent `category` a request needs. The
56
+ domain also matches its subdomains: `google-analytics.com` covers
57
+ `www.google-analytics.com`. `pathIncludes` narrows the rule to paths that
58
+ contain a substring, and `methods` narrows it to HTTP methods. A request is
59
+ blocked when a matching rule's condition is not met by the visitor's
60
+ effective permissions.
61
+
62
+ `category` takes the same conditions as scripts:
63
+
64
+ ```ts
65
+ { category: 'measurement' }
66
+ { category: { and: ['measurement', 'marketing'] } }
67
+ { category: { or: ['measurement', 'marketing'] } }
68
+ ```
69
+
70
+ Add `vendor` to also block the request while the visitor has turned that
71
+ vendor off. IAB TCF rules use `vendorId` and the `iab*` purpose fields
72
+ instead.
73
+
74
+ | Option | Default | Purpose |
75
+ | -------------------- | -------- | ------------------------------------------------------------ |
76
+ | `rules` | required | Rules described above |
77
+ | `enabled` | `true` | Set `false` to keep the rules but stop blocking |
78
+ | `logBlockedRequests` | `true` | Log each blocked request with `console.warn` |
79
+ | `onRequestBlocked` | none | Called with `{ method, url, rule }` for each blocked request |
80
+
81
+ ## What a blocked request looks like
82
+
83
+ The blocker wraps `window.fetch` and `XMLHttpRequest`. A blocked `fetch`
84
+ resolves to a `451` response with the status text
85
+ `Request blocked by consent`, and nothing is sent. A blocked XHR is aborted
86
+ and fires an `error` event. Requests that match no rule are not delayed.
87
+
88
+ ## When blocking starts
89
+
90
+ The provider holds matching requests from its first render in the browser,
91
+ before any of its children render or run effects. That covers requests from
92
+ child components, including their mount effects, and from effects in
93
+ components rendered next to the provider. The blocker module itself loads
94
+ after mount and decides each held request. Apps without `networkBlocker` do
95
+ not download it.
96
+
97
+ The standalone `useNetworkBlocker` hook works the same way from the first
98
+ render of the component that calls it. That render patches `fetch` and
99
+ `XMLHttpRequest`. If React throws the render away and never commits it, the
100
+ hold ends after 10 seconds. Nothing checked consent for the requests it held,
101
+ so they fail the way the blocker fails a blocked request: a 451 response for
102
+ `fetch`, a failed XHR. The same happens when the component unmounts before
103
+ the blocker loads.
104
+
105
+ While consent is unknown, a matching request that would be blocked waits
106
+ instead of failing. Consent is unknown until the policy has loaded, which is
107
+ also when a returning visitor's stored choice takes effect. The request is
108
+ then sent if the choice allows it and blocked otherwise. If the policy fails
109
+ to load, optional categories stay denied and waiting requests are blocked.
110
+ If the policy request never finishes, they keep waiting and are never sent.
111
+
112
+ A synchronous XHR cannot wait. Before the blocker module has loaded, a
113
+ matching one throws a `NetworkError` from `send()`. After that, one that
114
+ consent does not allow yet is blocked.
115
+
116
+ ## What the network blocker cannot stop
117
+
118
+ The blocker only sees requests made after the provider starts rendering in
119
+ the browser, through `fetch` or `XMLHttpRequest`. It cannot stop:
120
+
121
+ * Code that runs before the provider renders: inline scripts in the HTML,
122
+ third-party tags in `<head>`, scripts loaded before hydration (such as
123
+ `next/script` with `beforeInteractive`), and client modules that evaluate
124
+ earlier. Webpack builds evaluate a route's client component modules when
125
+ its chunk loads, so their top-level code runs first. Turbopack evaluates a
126
+ client component module when its first element renders, which inside the
127
+ provider is after blocking starts.
128
+ * Code that saved its own reference to `fetch` or `XMLHttpRequest` before the
129
+ provider rendered.
130
+ * `navigator.sendBeacon`, `WebSocket`, `EventSource`, and requests made by
131
+ `<img>`, `<script>` or `<iframe>` elements, web workers and service
132
+ workers.
133
+ * With the standalone `useNetworkBlocker` hook instead of the provider
134
+ option, requests sent before the component that calls it renders.
135
+ Blocking starts in that component's first render, not the provider's.
136
+
137
+ Keep tracking calls out of that window:
138
+
139
+ * Send them from an effect or an event handler, never at module top level.
140
+ * Load vendor SDKs through `scripts` instead of a `<script>` tag or
141
+ `next/script`, so they wait for consent before they run at all.
142
+ * Check `useConsent('measurement')` (or the category you need) before you
143
+ call a vendor from your own code, and treat the blocker as a backstop.
144
+
145
+ ## Verify the blocked requests
146
+
147
+ Open the production build in a private window with the DevTools Network panel
148
+ open, under a policy that asks for consent.
149
+
150
+ 1. Before a choice, requests matching a rule are absent from the Network panel,
151
+ and the console logs each blocked request.
152
+ 2. Allow the rule's category and save. Matching requests go out.
153
+ 3. Reject, reload, and check that they stay absent.
@@ -0,0 +1,196 @@
1
+ ---
2
+ title: Scripts
3
+ description: Register vendor scripts in a Next.js ConsentRoot, check how each
4
+ vendor loads, let visitors turn off one vendor and clear stored data after
5
+ revocation.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Install the script helpers
10
+
11
+ The [App Router](https://c15t.com/docs/frameworks/next/app-router),
12
+ [Pages Router](https://c15t.com/docs/frameworks/next/pages-router) and
13
+ [static export](https://c15t.com/docs/frameworks/next/static-export) guides already register
14
+ scripts. Use this page to add vendors to an existing setup or to change how
15
+ they load. Install the helpers if you have not:
16
+
17
+ | Package manager | Command |
18
+ | :-------------- | :------------------------------------- |
19
+ | npm | `npm install @c15t/integrations@alpha` |
20
+ | pnpm | `pnpm add @c15t/integrations@alpha` |
21
+ | yarn | `yarn add @c15t/integrations@alpha` |
22
+ | bun | `bun add @c15t/integrations@alpha` |
23
+
24
+ Keep `c15t.config.ts`, the manifest route and the layout from your router
25
+ guide. Adding a vendor needs no server change. Server-rendered state, a
26
+ streamed promise and browser initialization with `state={{}}` all reach the
27
+ same `ConsentRoot`, and no script loads until the browser has a resolved
28
+ policy and the visitor's choice allows it.
29
+
30
+ ## Register scripts in a client wrapper
31
+
32
+ The [runnable Next.js example](https://c15t.com/docs/examples) uses PostHog for measurement and
33
+ X Pixel for marketing. Replace the placeholder IDs `phc_your_project_key` and
34
+ `your-pixel-id` with your own. Remove a vendor's entry to leave it out, or
35
+ replace its helper with the [integration](../../integrations/overview.md) your
36
+ application uses. Include the
37
+ measurement and marketing categories in your policy for these two vendors.
38
+
39
+ Create `lib/scripts.ts` with the example's script configuration:
40
+
41
+ ```ts title="lib/scripts.ts"
42
+ import { posthog } from '@c15t/integrations/posthog';
43
+ import { xPixel } from '@c15t/integrations/x-pixel';
44
+ import type { Script } from 'c15t';
45
+
46
+ export const scripts: Script[] = [
47
+ posthog({
48
+ id: 'phc_your_project_key',
49
+ initOptions: { cookieless_mode: 'never' },
50
+ loadMode: 'after-consent',
51
+ }),
52
+ xPixel({ pixelId: 'your-pixel-id' }),
53
+ ];
54
+ ```
55
+
56
+ PostHog waits for measurement consent here. `cookieless_mode: 'never'` disables
57
+ cookieless capture after rejection. X Pixel waits for marketing consent. Remove
58
+ any existing loader for these vendors, including `next/script` and tag-manager
59
+ entries, so each integration loads once.
60
+
61
+ Create this client wrapper. It owns everything that has to run in the browser:
62
+ the consent config, the scripts, the banner, the dialog and a persistent
63
+ preferences link. The layout or `_app.tsx` passes only the visitor's state.
64
+
65
+ ```tsx title="components/consent.tsx"
66
+ 'use client';
67
+
68
+ import {
69
+ ConsentBanner,
70
+ ConsentDialog,
71
+ ConsentDialogLink,
72
+ ConsentRoot,
73
+ } from 'c15t/next';
74
+ import type { ConsentRootProps } from 'c15t/next';
75
+ import type { ReactNode } from 'react';
76
+
77
+ import { consentConfig } from '@/c15t.config';
78
+ import { scripts } from '@/lib/scripts';
79
+
80
+ interface ConsentProps {
81
+ children: ReactNode;
82
+ state: ConsentRootProps['state'];
83
+ }
84
+
85
+ export const Consent = ({ children, state }: ConsentProps) => (
86
+ <ConsentRoot state={state} config={consentConfig} scripts={scripts}>
87
+ {children}
88
+ <ConsentBanner />
89
+ <ConsentDialog />
90
+ <footer>
91
+ <ConsentDialogLink>Privacy settings</ConsentDialogLink>
92
+ </footer>
93
+ </ConsentRoot>
94
+ );
95
+ ```
96
+
97
+ Import `consentConfig` in this `'use client'` file, as shown, and do not pass it
98
+ from a Server Component. `defineConsentConfig` marks its result with a symbol
99
+ key, and React cannot send an object with symbol keys from a Server Component
100
+ to a Client Component.
101
+
102
+ `ConsentRoot` already provides the consent runtime. Mount this wrapper once and
103
+ do not add a second provider. Keep your site's content and footer inside it.
104
+
105
+ The files import through the `@/` path alias that `create-next-app` sets up.
106
+ With a `src/` directory the alias points at `src/`, so the same imports work.
107
+ Without the alias, use relative paths.
108
+
109
+ ## Register several vendors
110
+
111
+ Every helper goes in the same `scripts` array in `lib/scripts.ts`, in the App
112
+ Router and the Pages Router alike. This array loads Google Tag Manager and
113
+ Meta Pixel together:
114
+
115
+ ```ts title="lib/scripts.ts (partial)"
116
+ import { googleTagManager } from '@c15t/integrations/google-tag-manager';
117
+ import { metaPixel } from '@c15t/integrations/meta-pixel';
118
+ import type { Script } from 'c15t';
119
+
120
+ export const scripts: Script[] = [
121
+ googleTagManager({ id: 'GTM-XXXXXXX' }),
122
+ metaPixel({ pixelId: 'YOUR_PIXEL_ID' }),
123
+ ];
124
+ ```
125
+
126
+ Each helper keeps its own consent rules. Meta Pixel waits for marketing, and
127
+ Google Tag Manager follows the
128
+ [Consent Mode contract](../../integrations/google-tag-manager.md). Remove the
129
+ container and pixel snippets from `pages/_document.tsx` or your layout, so each
130
+ vendor loads once. Both helpers throw on an empty ID, so leave a helper out of
131
+ the array until you have its ID.
132
+
133
+ ## Check each vendor's loading behavior
134
+
135
+ The example configures PostHog to load after consent and turns off cookieless
136
+ capture. Its default helper can load before consent and use the SDK's own
137
+ consent controls. Read the [PostHog guide](../../integrations/posthog.md) before
138
+ you change those settings.
139
+
140
+ Ordinary scripts wait for their category's permission. Give each script a
141
+ stable, unique `id`, and remove any other loader for the same vendor. Helpers
142
+ with `alwaysLoad` can load an SDK before permission is granted, so a category
143
+ alone does not guarantee that no request happens. See the
144
+ [vendor guides](../../integrations/overview.md) for each helper's contract.
145
+
146
+ Removing a script element cannot undo JavaScript that already ran or requests
147
+ already sent. When a visitor turns off a category they had granted,
148
+ `ConsentRoot` reloads the page, so the next page runs only permitted code. See
149
+ [reload after revocation](https://c15t.com/docs/frameworks/next/components/consent-root#reload-after-revocation).
150
+
151
+ Use [custom integrations](../../integrations/building-integrations.md) for a
152
+ vendor without a helper. Google helpers follow the
153
+ [Consent Mode contract](../../integrations/google-tag-manager.md).
154
+
155
+ ## Embeds and other requests
156
+
157
+ Scripts cover vendor code c15t loads for you. For the rest:
158
+
159
+ * [Embeds](./embeds.md) keeps iframes out of the page with
160
+ `ConsentGate` or the iframe blocker.
161
+ * [Network blocker](./network-blocker.md) holds `fetch` and
162
+ XHR calls that match a rule until their category is allowed.
163
+
164
+ ## Let visitors turn off one vendor
165
+
166
+ A visitor can allow marketing and still switch off one vendor in it. Declare
167
+ the vendors and pass them to `ConsentRoot` as `vendors`; helpers from
168
+ `@c15t/integrations` already carry their vendor slug. See
169
+ [vendor consent](https://c15t.com/docs/frameworks/next/vendor-consent).
170
+
171
+ ## Clear stored tracking data
172
+
173
+ Script gating does not remove cookies or Web Storage entries a script already
174
+ wrote. Pass `clearOnRevocation` to `ConsentRoot` to remove declared data when
175
+ its category is denied. See
176
+ [clear on revocation](https://c15t.com/docs/frameworks/next/clear-on-revocation) for
177
+ configuration and browser limits.
178
+
179
+ To send your own events only to allowed integrations, see
180
+ [send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations).
181
+
182
+ ## Verify the integration
183
+
184
+ Open the production build in a fresh browser session with DevTools open.
185
+
186
+ 1. Under an opt-in policy, neither PostHog nor X Pixel requests anything before
187
+ you choose.
188
+ 2. Open **Privacy settings** and turn on **Analytics** (the `measurement` category) only. PostHog loads and
189
+ X Pixel stays blocked.
190
+ 3. Reject, reload, and reopen **Privacy settings**. The rejection is still
191
+ selected and neither vendor loads.
192
+ 4. Turn a granted category off. The page reloads and that vendor does not load
193
+ again.
194
+
195
+ The [runnable example](https://c15t.com/docs/examples) has the same script definitions, and
196
+ [the consent checks](../../guides/verify-consent.md) cover the release checklist.
@@ -0,0 +1,81 @@
1
+ ---
2
+ title: Embeds
3
+ description: Keep YouTube videos, maps and other iframes out of a Nuxt page
4
+ until their consent category is allowed, with ConsentGate or the iframe
5
+ blocker.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Pick a method
10
+
11
+ | Method | Use it when |
12
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
13
+ | `ConsentGate` | You render the iframe in a Vue component and want a placeholder with a way to open preferences. The server HTML respects the visitor's choice. |
14
+ | The iframe blocker | The iframe comes from markup you do not render with a component, such as CMS content, or you want no wrapper. |
15
+
16
+ Both keep the iframe's `src` out of the page until the category is allowed,
17
+ so the embed's host receives no request before consent.
18
+
19
+ ## Gate an embed with ConsentGate
20
+
21
+ Wrap the iframe in the globally registered
22
+ [`ConsentGate`](https://c15t.com/docs/frameworks/nuxt/components/consent-gate):
23
+
24
+ ```vue title="app/components/VideoEmbed.vue"
25
+ <template>
26
+ <ConsentGate category="measurement">
27
+ <iframe
28
+ src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y"
29
+ title="YouTube video"
30
+ loading="lazy"
31
+ allowfullscreen
32
+ />
33
+ <template #placeholder>
34
+ <p>Allow measurement to load this YouTube video.</p>
35
+ <ConsentPreferencesLink>Choose video permissions</ConsentPreferencesLink>
36
+ </template>
37
+ </ConsentGate>
38
+ </template>
39
+ ```
40
+
41
+ The iframe is absent from the server HTML and the DOM until the visitor
42
+ allows measurement. The `placeholder` slot shows a message and a
43
+ [`ConsentPreferencesLink`](https://c15t.com/docs/frameworks/nuxt/components/consent-preferences-link)
44
+ until then. When the visitor withdraws measurement, Vue removes the iframe.
45
+
46
+ Pick the category that matches what the embed does. The example uses
47
+ measurement for YouTube because its player measures views. A map or chat
48
+ widget usually belongs under functionality or experience.
49
+
50
+ ## Gate an iframe with the iframe blocker
51
+
52
+ The module runs the iframe blocker in the browser by default. Give an iframe
53
+ `data-src` instead of `src`, and a `data-category`:
54
+
55
+ ```vue title="app/components/MapEmbed.vue"
56
+ <template>
57
+ <iframe
58
+ data-src="https://www.google.com/maps/embed?pb=YOUR_EMBED_ID"
59
+ data-category="functionality"
60
+ title="Store map"
61
+ />
62
+ </template>
63
+ ```
64
+
65
+ The server HTML has no `src`, so nothing loads before hydration. After
66
+ hydration, c15t sets `src` from `data-src` when the category is allowed and
67
+ removes it when the category is withdrawn. It watches the page, so iframes
68
+ added by later navigation are handled too. The iframe element stays in the
69
+ page while blocked, empty. Add `data-vendor` with a vendor ID to also block
70
+ the iframe while the visitor has turned that vendor off.
71
+
72
+ To turn the blocker off, set `iframeBlocker: false` in the module options.
73
+
74
+ ## Verify
75
+
76
+ View the page source in a private window, under a policy that asks for
77
+ consent. No iframe has a `src` pointing at `youtube-nocookie.com` or
78
+ `google.com/maps`, and the Network tab shows no request to them. Allow the
79
+ category and save: the iframe loads. Withdraw it and save: the page reloads
80
+ without the embed. The [YouTube](../../integrations/youtube.md) and
81
+ [Google Maps](../../integrations/google-maps.md) guides cover sizing and titles.