@c15t/react 3.0.0-alpha.9 → 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.
- package/AGENTS.md +7 -9
- package/README.md +41 -70
- package/SKILL.md +1 -1
- package/dist/aggregate-components.js +1 -1
- package/dist/components/consent-gate/atoms.js +1 -1
- package/dist/components/consent-gate/frame.js +2 -0
- package/dist/components/consent-gate/index.js +1 -1
- package/dist/components/iab-panel/atoms/root.js +1 -1
- package/dist/components/iab-panel/iab-panel.js +1 -1
- package/dist/components/iab-prompt/atoms/root.js +1 -1
- package/dist/components/iab-prompt/iab-prompt.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/root.js +1 -1
- package/dist/components/panel-trigger/panel-trigger.js +1 -1
- package/dist/components/panel-trigger/toolbar-loader.js +2 -0
- package/dist/components/preferences/atoms/root.js +1 -1
- package/dist/components/prompt/atoms/early-tap-script.js +2 -0
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/shared/primitives/button.js +1 -1
- package/dist/components/shared/surface-styles.js +1 -1
- package/dist/consent-gate.js +1 -1
- package/dist/context/index.js +1 -1
- package/dist/devtools.js +1 -1
- package/dist/iab/styles.tw3.css +2 -2
- package/dist/iab-dialog-deferred.js +2 -0
- package/dist/iab.js +1 -1
- package/dist/index-parts/consent-gate.js +1 -1
- package/dist/index-parts/core.js +1 -1
- package/dist/index-parts/frame.js +1 -0
- package/dist/index-parts/hosted.js +1 -0
- package/dist/index-parts/offline.js +1 -0
- package/dist/index-parts/presets.js +1 -0
- package/dist/index.js +1 -1
- package/dist/modes.js +1 -0
- package/dist/panel-link.js +1 -1
- package/dist/panel-trigger.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/styles.tw3.css +2 -2
- package/dist/transports/hosted.js +1 -0
- package/dist/transports/manifest.js +1 -0
- package/dist/utils/deferred-module.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/chunk-warming.d.ts +7 -6
- package/dist-types/components/consent-gate/frame.d.ts +19 -0
- package/dist-types/components/consent-gate/index.d.ts +3 -19
- package/dist-types/components/iab-prompt/iab-prompt.d.ts +1 -1
- package/dist-types/components/panel/index.d.ts +1 -1
- package/dist-types/components/panel-trigger/toolbar-loader.d.ts +34 -0
- package/dist-types/components/preferences/index.d.ts +1 -1
- package/dist-types/components/prompt/atoms/early-tap-script.d.ts +17 -0
- package/dist-types/components/prompt/index.d.ts +1 -1
- package/dist-types/components/shared/surface-styles.d.ts +26 -2
- package/dist-types/consent-gate.d.ts +1 -1
- package/dist-types/context/index.d.ts +3 -3
- package/dist-types/context.d.ts +2 -0
- package/dist-types/gpp.d.ts +1 -1
- package/dist-types/iab-dialog-deferred.d.ts +36 -0
- package/dist-types/iab.d.ts +3 -2
- package/dist-types/index-parts/consent-gate.d.ts +1 -1
- package/dist-types/index-parts/core.d.ts +1 -2
- package/dist-types/index-parts/frame.d.ts +7 -0
- package/dist-types/index-parts/hosted.d.ts +7 -0
- package/dist-types/index-parts/offline.d.ts +7 -0
- package/dist-types/index-parts/presets.d.ts +7 -0
- package/dist-types/index.d.ts +5 -1
- package/dist-types/modes.d.ts +26 -0
- package/dist-types/panel-link.d.ts +1 -1
- package/dist-types/panel-trigger.d.ts +1 -1
- package/dist-types/provider.d.ts +3 -3
- package/dist-types/transports/hosted.d.ts +36 -0
- package/dist-types/transports/manifest.d.ts +48 -0
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +7 -9
- package/docs/concepts/choose-your-setup.md +45 -10
- package/docs/concepts/consent-categories.md +1 -1
- package/docs/concepts/consent-state.md +5 -2
- package/docs/concepts/data-fetching.md +100 -42
- package/docs/concepts/modes.md +281 -0
- package/docs/customization/dark-mode.md +1 -1
- package/docs/customization/motion.md +2 -2
- package/docs/customization/recipes.md +4 -4
- package/docs/customization/slots.md +25 -25
- package/docs/customization/stylesheets.md +12 -14
- package/docs/customization/tailwind.md +17 -11
- package/docs/customization/tokens.md +36 -7
- package/docs/frameworks/react/components/consent-banner.md +14 -10
- package/docs/frameworks/react/components/consent-dialog.md +14 -10
- package/docs/frameworks/react/components/consent-provider.md +51 -44
- package/docs/frameworks/react/components.md +14 -10
- package/docs/frameworks/react/content-security-policy.md +21 -12
- package/docs/frameworks/react/hooks.md +2 -2
- package/docs/frameworks/react/iab.md +20 -2
- package/docs/frameworks/react/quickstart.md +63 -97
- package/docs/frameworks/react/rendering.md +168 -94
- package/docs/frameworks/react/scripts.md +19 -28
- package/docs/frameworks/react/troubleshooting.md +57 -25
- package/docs/frameworks/react/upgrade-v3.md +110 -62
- package/docs/guides/banner-experiments.md +21 -6
- package/docs/guides/troubleshooting.md +89 -25
- package/docs/guides/verify-consent.md +23 -8
- package/docs/integrations/adobe-analytics.md +60 -64
- package/docs/integrations/ahrefs-analytics.md +58 -63
- package/docs/integrations/amplitude.md +60 -64
- package/docs/integrations/clearbit.md +59 -63
- package/docs/integrations/cloudflare-web-analytics.md +56 -60
- package/docs/integrations/cloudflare-zaraz.md +55 -57
- package/docs/integrations/crisp.md +64 -69
- package/docs/integrations/databuddy.md +62 -64
- package/docs/integrations/fathom-analytics.md +56 -60
- package/docs/integrations/front-chat.md +56 -60
- package/docs/integrations/google-maps.md +25 -13
- package/docs/integrations/google-tag-manager.md +153 -77
- package/docs/integrations/google-tag.md +134 -78
- package/docs/integrations/heap.md +56 -60
- package/docs/integrations/hightouch.md +55 -60
- package/docs/integrations/hotjar.md +60 -64
- package/docs/integrations/intercom.md +55 -60
- package/docs/integrations/klaviyo.md +65 -66
- package/docs/integrations/linkedin-insights.md +58 -63
- package/docs/integrations/logrocket.md +55 -60
- package/docs/integrations/matomo-analytics.md +58 -60
- package/docs/integrations/meta-pixel.md +56 -60
- package/docs/integrations/microsoft-clarity.md +57 -60
- package/docs/integrations/microsoft-uet.md +58 -61
- package/docs/integrations/mixpanel-analytics.md +73 -75
- package/docs/integrations/one-dollar-stats.md +53 -59
- package/docs/integrations/openai-pixel.md +58 -60
- package/docs/integrations/overview.md +59 -42
- package/docs/integrations/pinterest-tag.md +56 -60
- package/docs/integrations/pirsch.md +54 -60
- package/docs/integrations/plausible-analytics.md +55 -60
- package/docs/integrations/posthog.md +56 -60
- package/docs/integrations/promptwatch.md +57 -63
- package/docs/integrations/reddit-pixel.md +57 -60
- package/docs/integrations/rudderstack.md +65 -67
- package/docs/integrations/rybbit-analytics.md +70 -75
- package/docs/integrations/segment.md +56 -60
- package/docs/integrations/sentry.md +65 -60
- package/docs/integrations/snapchat-pixel.md +54 -60
- package/docs/integrations/tiktok-pixel.md +59 -63
- package/docs/integrations/umami-analytics.md +54 -60
- package/docs/integrations/vercel-analytics.md +53 -59
- package/docs/integrations/vercel-speed-insights.md +317 -0
- package/docs/integrations/x-pixel.md +57 -63
- package/docs/integrations/youtube.md +25 -13
- package/package.json +18 -8
- package/docs/upgrade-v3.md +0 -429
|
@@ -53,8 +53,7 @@ Global Vendor List response before changing rendering conditions.
|
|
|
53
53
|
|
|
54
54
|
IAB presentation follows `presentation.prompt.blocking` and
|
|
55
55
|
`presentation.preferences.blocking`. A blocking dialog needs focus management,
|
|
56
|
-
scroll locking and a backdrop together.
|
|
57
|
-
legacy option behavior.
|
|
56
|
+
scroll locking and a backdrop together.
|
|
58
57
|
|
|
59
58
|
## Mount IABProvider for every IAB policy
|
|
60
59
|
|
|
@@ -68,6 +67,25 @@ your error boundary or the framework's error overlay shows it. Render `IABProvid
|
|
|
68
67
|
`c15t/react/iab` for those visitors, or remove the `iab` model from the
|
|
69
68
|
policy.
|
|
70
69
|
|
|
70
|
+
## When the dialog loads
|
|
71
|
+
|
|
72
|
+
`IABConsentDialog` loads its code on demand, so the first page load
|
|
73
|
+
carries the banner without the preference center. The code downloads at
|
|
74
|
+
the first of:
|
|
75
|
+
|
|
76
|
+
* The browser's first idle period after the page's `load` event, while
|
|
77
|
+
the IAB banner is shown.
|
|
78
|
+
* Hover or focus on the banner's Customize button or partners link.
|
|
79
|
+
* The dialog opening.
|
|
80
|
+
|
|
81
|
+
A visitor who opens the dialog before the download finishes waits for
|
|
82
|
+
it. `open={true}` and `showTrigger` load it on mount, under a model the
|
|
83
|
+
dialog's `models` prop lists. Under another model, `showTrigger` loads
|
|
84
|
+
only the trigger. Set
|
|
85
|
+
`preloadDialog: 'intent'` in the consent provider options to skip the
|
|
86
|
+
idle download. Idle loading is also skipped with Save-Data on, on `2g`
|
|
87
|
+
and `slow-2g` connections, and while offline.
|
|
88
|
+
|
|
71
89
|
## Verify vendor and purpose choices
|
|
72
90
|
|
|
73
91
|
Test category choices alongside vendor, purpose, legitimate-interest and special
|
|
@@ -7,11 +7,10 @@ group: frameworks
|
|
|
7
7
|
|
|
8
8
|
## Before you start
|
|
9
9
|
|
|
10
|
-
This guide sets up a client-rendered React app built with Vite. The
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
the
|
|
14
|
-
request.
|
|
10
|
+
This guide sets up a client-rendered React app built with Vite. The browser
|
|
11
|
+
loads the page and asks your consent backend for the visitor's policy, then
|
|
12
|
+
shows the banner or runs the scripts the visitor allowed. The backend reads the
|
|
13
|
+
visitor's location from the request, so regional policies need no extra setup.
|
|
15
14
|
|
|
16
15
|
* **Next.js or TanStack Start?** Use that framework's guide from the
|
|
17
16
|
[framework list](https://c15t.com/docs/frameworks). Their adapters resolve consent on the
|
|
@@ -28,93 +27,52 @@ the backend URL.
|
|
|
28
27
|
[Choose your setup](../../concepts/choose-your-setup.md#decide-who-runs-the-consent-backend)
|
|
29
28
|
covers self-hosting and offline mode.
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
The guide uses `hosted()` mode. Policy, translation and vendor edits in your
|
|
31
|
+
project reach visitors on their next page load, without a rebuild.
|
|
33
32
|
|
|
34
33
|
## Install c15t
|
|
35
34
|
|
|
36
|
-
| Package manager | Command
|
|
37
|
-
| :-------------- |
|
|
38
|
-
| npm | `npm install c15t
|
|
39
|
-
| pnpm | `pnpm add c15t
|
|
40
|
-
| yarn | `yarn add c15t
|
|
41
|
-
| bun | `bun add c15t
|
|
35
|
+
| Package manager | Command |
|
|
36
|
+
| :-------------- | :------------------------------------ |
|
|
37
|
+
| npm | `npm install c15t @c15t/integrations` |
|
|
38
|
+
| pnpm | `pnpm add c15t @c15t/integrations` |
|
|
39
|
+
| yarn | `yarn add c15t @c15t/integrations` |
|
|
40
|
+
| bun | `bun add c15t @c15t/integrations` |
|
|
42
41
|
|
|
43
42
|
`c15t` contains the React adapter at `c15t/react` and the Vite plugin at
|
|
44
|
-
`c15t/build`. `@c15t/
|
|
45
|
-
`@c15t/integrations` holds the vendor loaders used below.
|
|
43
|
+
`c15t/build`. `@c15t/integrations` holds the vendor loaders used below.
|
|
46
44
|
|
|
47
|
-
##
|
|
45
|
+
## Add the Vite plugin
|
|
48
46
|
|
|
49
47
|
Add `consentManifest` to `vite.config.ts`:
|
|
50
48
|
|
|
51
49
|
```ts title="vite.config.ts"
|
|
52
50
|
import react from '@vitejs/plugin-react';
|
|
53
51
|
import { consentManifest } from 'c15t/build';
|
|
54
|
-
import { defineConfig
|
|
55
|
-
|
|
56
|
-
export default defineConfig(({ mode }) => {
|
|
57
|
-
const env = loadEnv(mode, process.cwd(), 'VITE_');
|
|
58
|
-
return {
|
|
59
|
-
plugins: [
|
|
60
|
-
react(),
|
|
61
|
-
// Downloads the policy and writes src/c15t-manifest.ts.
|
|
62
|
-
consentManifest({
|
|
63
|
-
backendURL:
|
|
64
|
-
env.VITE_C15T_BACKEND_URL ?? 'https://benchmarks-inth.inth.app',
|
|
65
|
-
}),
|
|
66
|
-
],
|
|
67
|
-
};
|
|
68
|
-
});
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
`https://benchmarks-inth.inth.app` is a demo project, so the snippet runs as
|
|
72
|
-
is. Replace it with your project's backend URL, exactly as Inth shows it,
|
|
73
|
-
including any path prefix, or set `VITE_C15T_BACKEND_URL` when you build.
|
|
74
|
-
|
|
75
|
-
`consentManifest` downloads your project's public policy from `/manifest` when
|
|
76
|
-
`vite build` or `vite dev` starts, and writes it to `src/c15t-manifest.ts`.
|
|
77
|
-
Add that file to `.gitignore`.
|
|
78
|
-
|
|
79
|
-
The snapshot is fixed at build time:
|
|
80
|
-
|
|
81
|
-
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
82
|
-
build output, force a fresh build.
|
|
83
|
-
* The build must reach the backend. A failed fetch stops Next.js, TanStack
|
|
84
|
-
Start, SvelteKit and Vite builds. By default, Astro and Nuxt log a warning
|
|
85
|
-
instead and fetch the policy at runtime.
|
|
86
|
-
* Consent choices still go to the backend.
|
|
87
|
-
|
|
88
|
-
Run `vite build` or `vite dev` before a standalone `tsc`, `vue-tsc` or
|
|
89
|
-
`svelte-check`; the generated file doesn't exist on a fresh checkout. Both
|
|
90
|
-
commands fetch the manifest when they start, so both need the backend.
|
|
91
|
-
`vite preview` serves the last build without fetching.
|
|
52
|
+
import { defineConfig } from 'vite';
|
|
92
53
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
Create `src/scripts.ts`. PostHog waits for measurement permission, so your
|
|
96
|
-
policy must include that category. Remove any other PostHog loader from your
|
|
97
|
-
app so PostHog loads once.
|
|
54
|
+
export default defineConfig({ plugins: [react(), consentManifest()] });
|
|
55
|
+
```
|
|
98
56
|
|
|
99
|
-
|
|
100
|
-
|
|
57
|
+
The plugin reads `VITE_C15T_BACKEND_URL`. Set it to your project's backend URL,
|
|
58
|
+
exactly as Inth shows it, including any path prefix, in `.env` or wherever you
|
|
59
|
+
build:
|
|
101
60
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
id: 'phc_your_project_key',
|
|
105
|
-
initOptions: { cookieless_mode: 'never' },
|
|
106
|
-
loadMode: 'after-consent',
|
|
107
|
-
}),
|
|
108
|
-
];
|
|
61
|
+
```sh title=".env"
|
|
62
|
+
VITE_C15T_BACKEND_URL=https://your-project.inth.app
|
|
109
63
|
```
|
|
110
64
|
|
|
111
|
-
|
|
112
|
-
|
|
65
|
+
If your app already sets `VITE_INTH_PROJECT_URL` for other Inth SDKs, that works
|
|
66
|
+
too. When both are set, `VITE_C15T_BACKEND_URL` wins.
|
|
113
67
|
|
|
114
|
-
`
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
68
|
+
`consentManifest` hands the backend URL to `hosted()` through
|
|
69
|
+
`c15t/generated`. No file is written into your project. Without the plugin,
|
|
70
|
+
pass the URL as `hosted({ backendURL })`.
|
|
71
|
+
|
|
72
|
+
The plugin also adds a small script to `index.html` that sends `/init` while
|
|
73
|
+
your JavaScript downloads, so the banner shows sooner.
|
|
74
|
+
[Rendering](./rendering.md#send-init-before-the-app-loads)
|
|
75
|
+
covers when to turn it off.
|
|
118
76
|
|
|
119
77
|
## Mount the provider
|
|
120
78
|
|
|
@@ -124,26 +82,30 @@ asks for them, and `ConsentDialogLink` keeps preferences reachable after the
|
|
|
124
82
|
banner closes.
|
|
125
83
|
|
|
126
84
|
```tsx title="src/consent.tsx"
|
|
127
|
-
import {
|
|
85
|
+
import { posthog } from '@c15t/integrations/posthog';
|
|
128
86
|
import {
|
|
129
87
|
ConsentBanner,
|
|
130
88
|
ConsentDialog,
|
|
131
89
|
ConsentDialogLink,
|
|
132
90
|
ConsentProvider,
|
|
91
|
+
hosted,
|
|
133
92
|
} from 'c15t/react';
|
|
134
93
|
import type { ReactNode } from 'react';
|
|
135
94
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
95
|
+
const options = {
|
|
96
|
+
// Asks VITE_C15T_BACKEND_URL for each visitor's policy.
|
|
97
|
+
mode: hosted(),
|
|
98
|
+
scripts: [
|
|
99
|
+
posthog({
|
|
100
|
+
id: 'phc_your_project_key',
|
|
101
|
+
initOptions: { cookieless_mode: 'never' },
|
|
102
|
+
loadMode: 'after-consent',
|
|
103
|
+
}),
|
|
104
|
+
],
|
|
105
|
+
};
|
|
144
106
|
|
|
145
107
|
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
146
|
-
<ConsentProvider options={
|
|
108
|
+
<ConsentProvider options={options}>
|
|
147
109
|
{children}
|
|
148
110
|
<ConsentBanner />
|
|
149
111
|
<ConsentDialog />
|
|
@@ -154,17 +116,21 @@ export const Consent = ({ children }: { children: ReactNode }) => (
|
|
|
154
116
|
);
|
|
155
117
|
```
|
|
156
118
|
|
|
157
|
-
`
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
be newer than the snapshot. Vite reads `VITE_C15T_BACKEND_URL` at build time
|
|
163
|
-
for both files, so set it before you build, not when you serve the files.
|
|
119
|
+
`hosted()` makes one `/init` request to your backend, started early by the
|
|
120
|
+
script in `index.html`. The backend picks the policy from the visitor's
|
|
121
|
+
location and language and returns its current version. Consent choices go to
|
|
122
|
+
the same backend. Vite reads `VITE_C15T_BACKEND_URL` at build time, so set it
|
|
123
|
+
before you build, not when you serve the files.
|
|
164
124
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
125
|
+
PostHog waits for measurement permission, so your policy must include that
|
|
126
|
+
category. Replace `phc_your_project_key` with your PostHog project key.
|
|
127
|
+
PostHog uses its EU host; add `region: 'us'` for a US project. Remove any
|
|
128
|
+
other PostHog loader from your app so PostHog loads once.
|
|
129
|
+
|
|
130
|
+
`loadMode: 'after-consent'` keeps the PostHog SDK off the page until the
|
|
131
|
+
visitor allows measurement. `cookieless_mode: 'never'` turns off PostHog's
|
|
132
|
+
cookieless capture. See [integrations](../../integrations/overview.md) for other
|
|
133
|
+
vendors.
|
|
168
134
|
|
|
169
135
|
Wrap your app with it in `src/main.tsx`:
|
|
170
136
|
|
|
@@ -214,7 +180,8 @@ Build and preview the production bundle with `vite build` and `vite preview`,
|
|
|
214
180
|
then open the site in a private window with DevTools open on the Network tab.
|
|
215
181
|
Test under a policy that asks for a choice, such as an EU opt-in policy.
|
|
216
182
|
|
|
217
|
-
1. **Before a choice.**
|
|
183
|
+
1. **Before a choice.** One `/init` request goes to your backend URL, starting
|
|
184
|
+
before your JavaScript finishes downloading, and the banner shows. There
|
|
218
185
|
are no requests to `posthog.com`.
|
|
219
186
|
2. **Reject All.** The banner closes and the vendor requests stay absent.
|
|
220
187
|
3. **Reload.** The banner stays closed and the vendor requests stay absent.
|
|
@@ -223,8 +190,7 @@ Test under a policy that asks for a choice, such as an EU opt-in policy.
|
|
|
223
190
|
save. PostHog's `array.js` loads.
|
|
224
191
|
5. **Turn Analytics off again.** The page reloads and PostHog does not load.
|
|
225
192
|
|
|
226
|
-
If the banner never appears, check the
|
|
227
|
-
request to `/init` on your backend URL. [Troubleshooting](./troubleshooting.md)
|
|
193
|
+
If the banner never appears, check the `/init` request to your backend URL. [Troubleshooting](./troubleshooting.md)
|
|
228
194
|
covers the common causes. [Verify consent](../../guides/verify-consent.md) has the
|
|
229
195
|
full release checklist.
|
|
230
196
|
|
|
@@ -23,92 +23,50 @@ read the visitor's cookie and location on the server, so the banner can be in
|
|
|
23
23
|
the first HTML. [Choose your setup](../../concepts/choose-your-setup.md#server-rendering-or-browser-rendering)
|
|
24
24
|
explains the difference in general terms.
|
|
25
25
|
|
|
26
|
-
## Bundle the manifest during Vite builds
|
|
27
|
-
|
|
28
|
-
The [quickstart](./quickstart.md) and `examples/react` in
|
|
29
|
-
the c15t repository use this setup.
|
|
30
|
-
|
|
31
|
-
The build fetches your public policy once and bundles it, so the server never
|
|
32
|
-
fetches it at runtime.
|
|
33
|
-
|
|
34
|
-
Add `consentManifest` to your existing Vite plugin list:
|
|
35
|
-
|
|
36
|
-
```ts title="vite.config.ts (plugins option)"
|
|
37
|
-
import { consentManifest } from 'c15t/build';
|
|
38
|
-
|
|
39
|
-
export default {
|
|
40
|
-
plugins: [
|
|
41
|
-
// Keep your existing framework plugins here.
|
|
42
|
-
consentManifest({ backendURL: 'https://your-project.inth.app' }),
|
|
43
|
-
],
|
|
44
|
-
};
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Vite writes `src/c15t-manifest.ts` before compiling, including at development
|
|
48
|
-
startup. Keep the generated file out of Git. Install `@c15t/browser@alpha` for
|
|
49
|
-
its browser manifest transport. In `src/consent.tsx`, create the provider's
|
|
50
|
-
mode with `manifest()` instead of `hosted()`:
|
|
51
|
-
|
|
52
|
-
```tsx title="src/consent.tsx"
|
|
53
|
-
import { manifest } from '@c15t/browser/headless';
|
|
54
|
-
|
|
55
|
-
import { consentManifest } from './c15t-manifest';
|
|
56
|
-
|
|
57
|
-
const mode = manifest({
|
|
58
|
-
backendURL: 'https://your-project.inth.app',
|
|
59
|
-
manifest: consentManifest,
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Keep passing `mode` to `ConsentProvider`. The browser resolves the snapshot
|
|
64
|
-
without `/manifest` when it has the geography the policy needs, or the policy
|
|
65
|
-
does not depend on geography. Supply known country and region through `inputs`
|
|
66
|
-
or overrides to keep regional resolution on the snapshot.
|
|
67
|
-
|
|
68
|
-
When the required country or region is missing, the transport calls backend
|
|
69
|
-
`/init` and uses its resolved policy and translations. That response can use
|
|
70
|
-
newer backend policy than the build snapshot. This is a full consent resolution,
|
|
71
|
-
so these visitors still wait for a backend round trip.
|
|
72
|
-
|
|
73
|
-
Policy and resolver code are part of the browser bundle.
|
|
74
|
-
|
|
75
|
-
The snapshot is fixed at build time:
|
|
76
|
-
|
|
77
|
-
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
78
|
-
build output, force a fresh build.
|
|
79
|
-
* The build must reach the backend. A failed fetch stops Next.js, TanStack
|
|
80
|
-
Start, SvelteKit and Vite builds. By default, Astro and Nuxt log a warning
|
|
81
|
-
instead and fetch the policy at runtime.
|
|
82
|
-
* Consent choices still go to the backend.
|
|
83
|
-
|
|
84
|
-
Run `vite build` or `vite dev` before a standalone `tsc`, `vue-tsc` or
|
|
85
|
-
`svelte-check`; the generated file doesn't exist on a fresh checkout. Both
|
|
86
|
-
commands fetch the manifest when they start, so both need the backend.
|
|
87
|
-
`vite preview` serves the last build without fetching.
|
|
88
|
-
|
|
89
|
-
For policy updates without a rebuild, use `hosted({ url })` from `c15t/react`
|
|
90
|
-
as the mode and omit the build plugin. That setup resolves policy through the
|
|
91
|
-
backend `/init` on each page load.
|
|
92
|
-
|
|
93
26
|
## Client-rendered apps
|
|
94
27
|
|
|
95
|
-
The
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
28
|
+
The [quickstart](./quickstart.md) and `examples/react` in
|
|
29
|
+
the c15t repository use `hosted()` from `c15t/react`. It reads the backend URL
|
|
30
|
+
`consentManifest` read from `VITE_C15T_BACKEND_URL` or
|
|
31
|
+
`VITE_INTH_PROJECT_URL`; without the plugin, pass `hosted({ backendURL })`.
|
|
32
|
+
Policy edits apply on the next page load, without a rebuild. The page loads
|
|
33
|
+
like this:
|
|
100
34
|
|
|
101
35
|
1. The page loads with no consent state. Every optional category is denied.
|
|
102
|
-
2.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
36
|
+
2. The browser requests `GET /init` from your backend URL. A
|
|
37
|
+
[script in `index.html`](#send-init-before-the-app-loads) sends it while
|
|
38
|
+
the JavaScript downloads. The backend picks the visitor's policy from their
|
|
39
|
+
location. With an `experiment`, pass it to the plugin as
|
|
40
|
+
`initPrefetch: { experiment }` so the early request carries the visitor's
|
|
41
|
+
arm. Without that, the provider sends its own `/init` when it mounts.
|
|
106
42
|
3. The provider applies the policy and any stored choice. It shows the banner
|
|
107
43
|
if the policy asks for one, and loads the scripts the visitor allowed.
|
|
108
44
|
|
|
109
45
|
Until step 3 finishes, gated scripts and `ConsentGate` embeds stay blocked. If
|
|
110
46
|
the backend request fails, they stay blocked and no banner shows.
|
|
111
47
|
|
|
48
|
+
### Send `/init` before the app loads
|
|
49
|
+
|
|
50
|
+
`consentManifest()` adds a small inline script to the `<head>` of
|
|
51
|
+
`index.html`. It sends `/init` while the browser downloads your JavaScript,
|
|
52
|
+
and `hosted()` uses that response instead of sending its own. The banner
|
|
53
|
+
usually shows 50 to 250 ms sooner. There is nothing to configure.
|
|
54
|
+
|
|
55
|
+
The early request can't read your app's options, so in a few setups it won't
|
|
56
|
+
match and the app sends a second `/init`. Pass `initPrefetch: false` when the
|
|
57
|
+
app uses `offline()` or gives `hosted()` its own `backendURL`, `fetch`,
|
|
58
|
+
`initURL` or `headers`. With its own `fetch`, `hosted()` sends `/init` through
|
|
59
|
+
it and ignores the early response. If it sets `credentials`, `overrides`, `storageKey`, an
|
|
60
|
+
`experiment`, or `journey: false`, pass the same values as
|
|
61
|
+
`initPrefetch: { credentials, experiment, journey, overrides, storageKey }`.
|
|
62
|
+
With `journey: 'tab'`, passing it too makes a tab's first `/init` report the
|
|
63
|
+
`'tab'` scope instead of `'page'`. It doesn't save a request.
|
|
64
|
+
Pass `experiment` only when c15t assigns the arm. If a feature flag picks
|
|
65
|
+
it, leave it out, or the script sends an arm the visitor may never see.
|
|
66
|
+
The dev server adds the script even to a `manifest()` app, which then sends
|
|
67
|
+
one `/init` per page load that it doesn't use. A production build leaves it
|
|
68
|
+
out.
|
|
69
|
+
|
|
112
70
|
## React Router, Remix and other server-rendered apps
|
|
113
71
|
|
|
114
72
|
React Router framework mode, Remix and similar frameworks have no c15t adapter.
|
|
@@ -119,23 +77,26 @@ around the outlet. In React Router framework mode that is `app/root.tsx`.
|
|
|
119
77
|
browser takes over after hydration:
|
|
120
78
|
|
|
121
79
|
* The server HTML contains your page but no banner and no consent state.
|
|
122
|
-
* After hydration the browser
|
|
123
|
-
|
|
124
|
-
missing, it calls `/init` and uses the backend's current policy and
|
|
125
|
-
translations, which can differ from the build snapshot. With `hosted()`,
|
|
126
|
-
it always calls `/init`.
|
|
80
|
+
* After hydration the browser calls `/init`, then shows the banner if the
|
|
81
|
+
policy asks for one.
|
|
127
82
|
* The server render and the first browser render agree, so hydration does not
|
|
128
83
|
warn or change a stored choice.
|
|
129
84
|
|
|
130
85
|
The Vite setup needs no change. The banner renders in the browser and brings
|
|
131
86
|
its own `<style>` element, so there is no stylesheet to import.
|
|
132
87
|
|
|
88
|
+
These frameworks render their own HTML, so the Vite plugin can't add the
|
|
89
|
+
early `/init` script. Put `buildPrefetchScript({ backendURL })` from `c15t` in
|
|
90
|
+
a `<script>` in the root `<head>` instead, unless your loader supplies
|
|
91
|
+
`initialData` as described below. The two are alternatives: with
|
|
92
|
+
`initialData` the provider never reads the script's response.
|
|
93
|
+
|
|
133
94
|
### Skip the browser's first init request
|
|
134
95
|
|
|
135
96
|
This is optional. `fetchSSRData` from `c15t/react/server` calls the backend's
|
|
136
97
|
`/init` from your server loader, forwarding the visitor's location and
|
|
137
98
|
language headers. Pass the result to the browser, wrap it in a promise and give
|
|
138
|
-
it to `hosted({
|
|
99
|
+
it to `hosted({ backendURL, initialData })`. The provider's first init then uses that
|
|
139
100
|
response instead of a network request.
|
|
140
101
|
|
|
141
102
|
Know its limits before you add it:
|
|
@@ -171,9 +132,9 @@ browser path.
|
|
|
171
132
|
## Static and prerendered pages
|
|
172
133
|
|
|
173
134
|
A prerendered page is the same HTML for every visitor, so it cannot contain one
|
|
174
|
-
visitor's consent. Use the quickstart's
|
|
175
|
-
|
|
176
|
-
|
|
135
|
+
visitor's consent. Use the quickstart's setup. `/init` requests and consent
|
|
136
|
+
saves go to the backend URL directly, so the static host needs no server
|
|
137
|
+
route. Add the site's
|
|
177
138
|
origin to the trusted origins in your Inth project.
|
|
178
139
|
|
|
179
140
|
Do not cache a response that contains a visitor's consent in a CDN or a shared
|
|
@@ -182,17 +143,13 @@ cache.
|
|
|
182
143
|
|
|
183
144
|
## Where the browser sends requests
|
|
184
145
|
|
|
185
|
-
With `hosted(
|
|
146
|
+
With `hosted()`, the browser calls two backend endpoints:
|
|
186
147
|
|
|
187
148
|
| Request | When |
|
|
188
149
|
| --------------------- | ------------------------------------------ |
|
|
189
150
|
| `GET {url}/init` | Once per page load, to resolve the policy |
|
|
190
151
|
| `POST {url}/subjects` | When the visitor accepts, rejects or saves |
|
|
191
152
|
|
|
192
|
-
With a bundled manifest, `manifest({ backendURL })` sends the same
|
|
193
|
-
`/subjects` request, and requests `/init` only when the policy depends on a
|
|
194
|
-
country or region the browser does not know.
|
|
195
|
-
|
|
196
153
|
These requests go to the backend's origin, so it must list your site as a
|
|
197
154
|
trusted origin.
|
|
198
155
|
[Data fetching](../../concepts/data-fetching.md) compares this with proxies and
|
|
@@ -203,9 +160,126 @@ custom transports.
|
|
|
203
160
|
1. Open the page in a private window and view the page source. In a
|
|
204
161
|
server-rendered app the HTML contains your content and no element with
|
|
205
162
|
`data-testid="consent-banner-root"`.
|
|
206
|
-
2. In DevTools Network, confirm
|
|
207
|
-
|
|
208
|
-
to resolve consent when geography is missing. With `hosted()`, one `/init`
|
|
209
|
-
request runs unless you supply `initialData`.
|
|
163
|
+
2. In DevTools Network, confirm one `/init` request runs, unless you supply
|
|
164
|
+
`initialData`.
|
|
210
165
|
3. The banner appears once the policy resolves. Vendor requests stay absent
|
|
211
166
|
until you allow their category.
|
|
167
|
+
|
|
168
|
+
## Bundle the policy at build time
|
|
169
|
+
|
|
170
|
+
`manifest()` resolves the policy in the browser from a snapshot the build
|
|
171
|
+
downloaded. It saves the `/init` request only in these cases:
|
|
172
|
+
|
|
173
|
+
* The policy has no location rules, or every location gets the same banner.
|
|
174
|
+
* The page already knows the visitor's location, for example from an edge
|
|
175
|
+
worker, and passes it as `inputs`.
|
|
176
|
+
* You run a same-origin `geoURL` route that returns the location.
|
|
177
|
+
|
|
178
|
+
Otherwise the browser still calls `/init` on a first visit and uses the
|
|
179
|
+
backend's answer, so the bundle adds bytes without saving a request.
|
|
180
|
+
`consentManifest` warns during the build when that happens. The resolver adds
|
|
181
|
+
about 3 KB gzipped to the bundle, and policy edits need a rebuild. The browser
|
|
182
|
+
sends no `/init`, so the backend doesn't count visitors it resolves this way.
|
|
183
|
+
|
|
184
|
+
The build fetches your public policy once and bundles it, so the server never
|
|
185
|
+
fetches it at runtime.
|
|
186
|
+
|
|
187
|
+
Keep `consentManifest` in your Vite plugin list:
|
|
188
|
+
|
|
189
|
+
```ts title="vite.config.ts (plugins option)"
|
|
190
|
+
import { consentManifest } from 'c15t/build';
|
|
191
|
+
|
|
192
|
+
export default {
|
|
193
|
+
plugins: [
|
|
194
|
+
// Keep your existing framework plugins here.
|
|
195
|
+
consentManifest({ backendURL: 'https://your-project.inth.app' }),
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Without `backendURL`, the plugin reads `VITE_C15T_BACKEND_URL`, then
|
|
201
|
+
`VITE_INTH_PROJECT_URL`. The plugin
|
|
202
|
+
fetches the policy during `vite build`, or in `vite dev` when the app first
|
|
203
|
+
loads it, and serves it as `snapshot` from `c15t/generated`. A build fetches
|
|
204
|
+
only when the app uses `manifest()`. In `src/consent.tsx`, create the
|
|
205
|
+
provider's mode with `manifest()` from `c15t/react` instead of `hosted()`. It
|
|
206
|
+
reads the snapshot and the backend URL from `c15t/generated`:
|
|
207
|
+
|
|
208
|
+
```tsx title="src/consent.tsx"
|
|
209
|
+
import { manifest } from 'c15t/react';
|
|
210
|
+
|
|
211
|
+
const mode = manifest();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Keep passing `mode` to `ConsentProvider`. The browser resolves the snapshot
|
|
215
|
+
without `/manifest` when it has the geography the policy needs, or the policy
|
|
216
|
+
does not depend on geography. Supply known country and region through `inputs`
|
|
217
|
+
or overrides to keep regional resolution on the snapshot.
|
|
218
|
+
|
|
219
|
+
When the build has no snapshot, as in `vite dev` after a failed fetch,
|
|
220
|
+
`snapshot` is `undefined`, and the browser fetches
|
|
221
|
+
`${backendURL}/manifest` instead.
|
|
222
|
+
|
|
223
|
+
When the required country or region is missing, the transport calls backend
|
|
224
|
+
`/init` and uses its resolved policy and translations. That response can use
|
|
225
|
+
newer backend policy than the build snapshot. `ConsentProvider` sends that
|
|
226
|
+
request while it first renders, before the page mounts, unless the visitor has
|
|
227
|
+
a stored choice.
|
|
228
|
+
|
|
229
|
+
The browser cannot guess the banner from the snapshot alone when locations
|
|
230
|
+
differ. With a European opt-in rule and no banner elsewhere, a banner shown
|
|
231
|
+
before `/init` answers would vanish for most of the world. The exception is a
|
|
232
|
+
policy whose country and region rules all show the same banner, with a default
|
|
233
|
+
rule that covers everywhere else: the browser resolves it at once.
|
|
234
|
+
|
|
235
|
+
Policy and resolver code are part of the browser bundle.
|
|
236
|
+
|
|
237
|
+
The snapshot is fixed at build time:
|
|
238
|
+
|
|
239
|
+
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
240
|
+
build output, force a fresh build.
|
|
241
|
+
* Consent choices still go to the backend.
|
|
242
|
+
|
|
243
|
+
The build reads the backend URL from your public backend URL variable when
|
|
244
|
+
the config doesn't pass one: `NEXT_PUBLIC_C15T_BACKEND_URL` in Next.js,
|
|
245
|
+
`NUXT_PUBLIC_C15T_BACKEND_URL` in Nuxt, `PUBLIC_C15T_BACKEND_URL` in Astro,
|
|
246
|
+
Svelte and SvelteKit, and `VITE_C15T_BACKEND_URL` in TanStack Start and other
|
|
247
|
+
Vite apps. Each also reads the matching Inth variable, such as
|
|
248
|
+
`NEXT_PUBLIC_INTH_PROJECT_URL`, when the c15t one is unset. See
|
|
249
|
+
[set the backend URL](../../concepts/modes.md#set-the-backend-url).
|
|
250
|
+
|
|
251
|
+
The fetch waits at most 10 seconds. When it fails, or no backend URL is set,
|
|
252
|
+
every framework does the same thing:
|
|
253
|
+
|
|
254
|
+
| Command | Default when the fetch fails |
|
|
255
|
+
| ------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
256
|
+
| Production build: `next build`, `vite build`, `nuxt build`, `astro build` | The build stops with an error. |
|
|
257
|
+
| Dev: `next dev`, `vite dev`, `nuxt dev`, `astro dev` | A warning, and the server fetches the policy at runtime. |
|
|
258
|
+
|
|
259
|
+
Set `onBuildError` to use one behaviour for both. `'fail'` stops dev too.
|
|
260
|
+
`'runtime'` lets a production build finish, and the server fetches the policy
|
|
261
|
+
at runtime. The `C15T_ON_BUILD_ERROR` environment variable overrides the
|
|
262
|
+
option, so you can deploy during a backend outage without a code change:
|
|
263
|
+
|
|
264
|
+
```sh
|
|
265
|
+
C15T_ON_BUILD_ERROR=runtime npm run build
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Turborepo's strict environment mode hides undeclared variables from tasks, so
|
|
269
|
+
list `C15T_ON_BUILD_ERROR` in the build task's `passThroughEnv` there.
|
|
270
|
+
|
|
271
|
+
The build skips the fetch, without an error, when it can't use a snapshot,
|
|
272
|
+
for example when the backend URL is relative. With `onBuildError: 'fail'`, a
|
|
273
|
+
relative URL stops the build. [Consent modes](../../concepts/modes.md#what-happens-when-the-download-fails)
|
|
274
|
+
lists every case.
|
|
275
|
+
|
|
276
|
+
`vite build` and `vite dev` fetch the manifest when they start. `vite preview`
|
|
277
|
+
serves the last build without fetching. The plugin writes no file into your
|
|
278
|
+
app, so there is nothing to keep out of Git. `c15t/generated` ships its own
|
|
279
|
+
types, so `tsc`, `vue-tsc` and `svelte-check` pass on a fresh checkout without
|
|
280
|
+
a build first.
|
|
281
|
+
|
|
282
|
+
The plugin can't see the options your app passes to `manifest()`. When the
|
|
283
|
+
app passes `source: 'runtime'` or `manifestURL`, set
|
|
284
|
+
`consentManifest({ source: 'runtime' })` too. The build then downloads no
|
|
285
|
+
manifest, so it doesn't fail when the backend's `/manifest` is down.
|
|
@@ -11,43 +11,33 @@ group: frameworks
|
|
|
11
11
|
Pass vendor loaders to `ConsentProvider` through `options.scripts`. The
|
|
12
12
|
provider loads each script when its category is allowed and removes it when
|
|
13
13
|
the category is denied. The [quickstart](./quickstart.md)
|
|
14
|
-
registers PostHog this way
|
|
15
|
-
|
|
16
|
-
```ts title="src/scripts.ts"
|
|
17
|
-
import { posthog } from '@c15t/integrations/posthog';
|
|
18
|
-
|
|
19
|
-
export const scripts = [
|
|
20
|
-
posthog({
|
|
21
|
-
id: 'phc_your_project_key',
|
|
22
|
-
initOptions: { cookieless_mode: 'never' },
|
|
23
|
-
loadMode: 'after-consent',
|
|
24
|
-
}),
|
|
25
|
-
];
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
The provider in `src/consent.tsx` receives them:
|
|
14
|
+
registers PostHog this way in `src/consent.tsx`:
|
|
29
15
|
|
|
30
16
|
```tsx title="src/consent.tsx"
|
|
31
|
-
import {
|
|
17
|
+
import { posthog } from '@c15t/integrations/posthog';
|
|
32
18
|
import {
|
|
33
19
|
ConsentBanner,
|
|
34
20
|
ConsentDialog,
|
|
35
21
|
ConsentDialogLink,
|
|
36
22
|
ConsentProvider,
|
|
23
|
+
hosted,
|
|
37
24
|
} from 'c15t/react';
|
|
38
25
|
import type { ReactNode } from 'react';
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
27
|
+
const options = {
|
|
28
|
+
// Asks VITE_C15T_BACKEND_URL for each visitor's policy.
|
|
29
|
+
mode: hosted(),
|
|
30
|
+
scripts: [
|
|
31
|
+
posthog({
|
|
32
|
+
id: 'phc_your_project_key',
|
|
33
|
+
initOptions: { cookieless_mode: 'never' },
|
|
34
|
+
loadMode: 'after-consent',
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
};
|
|
48
38
|
|
|
49
39
|
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
50
|
-
<ConsentProvider options={
|
|
40
|
+
<ConsentProvider options={options}>
|
|
51
41
|
{children}
|
|
52
42
|
<ConsentBanner />
|
|
53
43
|
<ConsentDialog />
|
|
@@ -73,9 +63,10 @@ covers a vendor without one.
|
|
|
73
63
|
ran keeps running, so after the visitor turns off a category they had
|
|
74
64
|
allowed, the provider reloads the page once the save finishes. Set
|
|
75
65
|
`reloadOnConsentRevoked: false` only if every gated vendor stops itself.
|
|
76
|
-
* **`alwaysLoad` helpers.** Some integrations, such as Google Consent Mode
|
|
77
|
-
load before consent and pass the visitor's choice to the
|
|
78
|
-
vendor's guide; a category on a script does not always mean
|
|
66
|
+
* **`alwaysLoad` helpers.** Some integrations, such as the Google Consent Mode
|
|
67
|
+
helpers by default, load before consent and pass the visitor's choice to the
|
|
68
|
+
vendor. Read the vendor's guide; a category on a script does not always mean
|
|
69
|
+
zero requests.
|
|
79
70
|
|
|
80
71
|
## When the script loader downloads
|
|
81
72
|
|