@c15t/react 3.0.0-alpha.10 → 3.0.0-alpha.11

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 (45) hide show
  1. package/AGENTS.md +1 -0
  2. package/dist/aggregate-components.js +1 -1
  3. package/dist/chunk-warming.js +1 -1
  4. package/dist/components/consent-gate/atoms.js +1 -1
  5. package/dist/components/iab-panel/atoms/root.js +1 -1
  6. package/dist/components/iab-panel/iab-panel.js +1 -1
  7. package/dist/components/iab-prompt/atoms/root.js +1 -1
  8. package/dist/components/iab-prompt/iab-prompt.js +1 -1
  9. package/dist/components/panel/atoms/root.js +1 -1
  10. package/dist/components/panel-trigger/atoms/root.js +1 -1
  11. package/dist/components/panel-trigger/panel-trigger.js +1 -1
  12. package/dist/components/panel-trigger/toolbar-loader.js +2 -0
  13. package/dist/components/preferences/atoms/root.js +1 -1
  14. package/dist/components/prompt/atoms/root.js +1 -1
  15. package/dist/components/shared/surface-styles.js +1 -1
  16. package/dist/devtools.js +1 -1
  17. package/dist/iab-dialog-deferred.js +2 -0
  18. package/dist/iab.js +1 -1
  19. package/dist/utils/deferred-module.js +1 -1
  20. package/dist/version.js +1 -1
  21. package/dist-types/chunk-warming.d.ts +8 -11
  22. package/dist-types/components/panel-trigger/toolbar-loader.d.ts +34 -0
  23. package/dist-types/components/shared/surface-styles.d.ts +26 -2
  24. package/dist-types/iab-dialog-deferred.d.ts +36 -0
  25. package/dist-types/iab.d.ts +2 -1
  26. package/dist-types/version.d.ts +1 -1
  27. package/docs/README.md +1 -0
  28. package/docs/concepts/choose-your-setup.md +5 -2
  29. package/docs/concepts/data-fetching.md +24 -10
  30. package/docs/concepts/modes.md +9 -6
  31. package/docs/frameworks/react/components/consent-banner.md +3 -3
  32. package/docs/frameworks/react/components/consent-dialog.md +3 -3
  33. package/docs/frameworks/react/components/consent-provider.md +12 -9
  34. package/docs/frameworks/react/components.md +3 -3
  35. package/docs/frameworks/react/content-security-policy.md +8 -2
  36. package/docs/frameworks/react/iab.md +19 -0
  37. package/docs/frameworks/react/quickstart.md +25 -79
  38. package/docs/frameworks/react/rendering.md +161 -139
  39. package/docs/frameworks/react/scripts.md +3 -3
  40. package/docs/frameworks/react/troubleshooting.md +17 -16
  41. package/docs/frameworks/react/upgrade-v3.md +5 -4
  42. package/docs/guides/banner-experiments.md +16 -0
  43. package/docs/integrations/overview.md +1 -0
  44. package/docs/integrations/vercel-speed-insights.md +306 -0
  45. package/package.json +7 -7
@@ -14,14 +14,14 @@ the same protocol, so every path below works with either. Only `offline()`
14
14
  removes backend requests. For a recommendation by framework, start with
15
15
  [choose your setup](./choose-your-setup.md).
16
16
 
17
- | Fetching path | Where policy resolves | Where choices are submitted | Choose it when |
18
- | -------------------------------- | ------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------ |
19
- | Build-time manifest, recommended | Your server or browser resolves a bundled policy snapshot with each visitor's inputs | Inth or your c15t backend | Your framework supports a build integration and policy changes can ship with a rebuild |
20
- | Cached manifest on your server | Your application resolves public policy data with each request's location and signals | Inth or your c15t backend | Policy changes must reach the app without rebuilding, or builds cannot access the backend |
21
- | Regular backend `/init` | The consent backend | The same backend | You want the fewest moving parts, need backend-owned request resolution, or have no application server |
22
- | Manifest in the browser | The browser, using supplied or unknown location | Inth or your c15t backend | You deliberately want client resolution and have planned geography, bundle size and policy refresh |
23
- | Offline | The browser or local runtime, using bundled rules | No backend submission | Local development and tests. Not recommended for production environments. |
24
- | Custom transport | Your implementation | Your implementation | An existing service cannot use the c15t backend protocol |
17
+ | Fetching path | Where policy resolves | Where choices are submitted | Choose it when |
18
+ | --------------------------------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | Build-time manifest, recommended for server-rendered apps | Your server resolves a bundled policy snapshot with each visitor's inputs | Inth or your c15t backend | Your framework renders on the server, supports a build integration, and policy changes can ship with a rebuild |
20
+ | Cached manifest on your server | Your application resolves public policy data with each request's location and signals | Inth or your c15t backend | Policy changes must reach the app without rebuilding, or builds cannot access the backend |
21
+ | Regular backend `/init` | The consent backend | The same backend | You have no application server, as in a React, Vue or Svelte single-page app, want the fewest moving parts, or need backend-owned request resolution |
22
+ | Manifest in the browser | The browser, using a supplied location, or `/init` when the policy needs one it doesn't have | Inth or your c15t backend | The policy has no location rules or the page knows the visitor's location, and you accept about 3 KB more bundle and a rebuild for policy edits |
23
+ | Offline | The browser or local runtime, using bundled rules | No backend submission | Local development and tests. Not recommended for production environments. |
24
+ | Custom transport | Your implementation | Your implementation | An existing service cannot use the c15t backend protocol |
25
25
 
26
26
  ## What is a consent manifest?
27
27
 
@@ -53,7 +53,9 @@ download fails.
53
53
  The framework quickstarts include the build integration for supported
54
54
  deployments. Server resolution uses the snapshot without an upstream manifest
55
55
  request. Browser resolution includes the policy and resolver in the client
56
- bundle, and may still request location for regional policies. Static Nuxt and
56
+ bundle, and still calls `/init` for a regional policy when the browser doesn't
57
+ know the visitor's location, so the React, Vue and Svelte quickstarts use
58
+ `hosted()` instead. Static Nuxt and
57
59
  Astro sites and script-tag sites keep the runtime setup from their guides.
58
60
 
59
61
  The snapshot is fixed at build time:
@@ -133,7 +135,11 @@ Saves replayed after going offline carry none.
133
135
 
134
136
  Set it on the runtime or provider. In Next.js use `defineConsentConfig({ journey })`;
135
137
  in TanStack Start pass the same value to `resolveConsent` and `ConsentRoot`; with
136
- the inline prefetch script, pass it to `buildPrefetchScript`. Vue, Svelte, Astro
138
+ the inline prefetch script, pass it to `buildPrefetchScript` or to
139
+ `consentManifest({ initPrefetch: { journey } })`. Left unset, the script
140
+ continues a `'tab'` journey the tab already holds. A tab's first page has
141
+ none, so its `/init` reports the scope as `'page'`. The id still links it to
142
+ the save. Pass `journey: 'tab'` to the script too if you report on scope. Vue, Svelte, Astro
137
143
  and the script tag use `'page'`. A manifest that resolves locally in the
138
144
  browser reports no init, so it sends no journey.
139
145
 
@@ -161,6 +167,14 @@ A same-origin URL named `/api/c15t/init` can instead resolve from a cached
161
167
  manifest in your application. The URL name alone does not tell you which path
162
168
  runs.
163
169
 
170
+ In the browser, an inline script in `<head>` can start `/init` while the
171
+ app's JavaScript downloads, and `hosted()` uses that response. Vite apps get
172
+ it from `consentManifest()` by default, Nuxt adds it to pages with
173
+ `ssr: false`, and TanStack Start has `consentPrefetchHead`. Elsewhere, render
174
+ `buildPrefetchScript({ backendURL })` from `c15t` yourself. If c15t assigns
175
+ a banner experiment's arm, give the script the same `experiment`, or
176
+ `hosted()` sends a second `/init` for visitors who have not chosen yet.
177
+
164
178
  ## How do transports and proxies differ?
165
179
 
166
180
  A transport implements initialization, saving and optional record operations.
@@ -15,15 +15,18 @@ and `offline()`. Consent choices go to the backend in `manifest()` and
15
15
  `hosted()`, whether that backend is [Inth](https://inth.com) or a
16
16
  [self-hosted c15t backend](https://v3.c15t.com/docs/self-host/quickstart).
17
17
 
18
- | Mode | Where the policy resolves | Backend requests on a first visit | Use it when |
19
- | ------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
20
- | `manifest()`, the default | Your server, from the policy the build downloaded. With `resolve: 'browser'`, or in a single-page app, the browser. | None to resolve the policy. Choices post to `/subjects`. | Most apps. Rebuild to ship policy changes, or set `source: 'runtime'`. |
21
- | `hosted()` | The backend's `GET /init` | One `/init` per page that resolves consent. | Static hosting without a server, or a policy that changes without a rebuild and needs the backend's geolocation. |
22
- | `offline()` | The browser, from rules in your bundle | None. Choices stay in the browser. | Local development, tests and demos. Not recommended for production environments. |
18
+ | Mode | Where the policy resolves | Backend requests on a first visit | Use it when |
19
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
+ | `manifest()`, the default | Your server, from the policy the build downloaded. With `resolve: 'browser'`, or in a single-page app, the browser. | None to resolve the policy, unless the browser needs a location it doesn't have. Choices post to `/subjects`. | Server-rendered apps. Rebuild to ship policy changes, or set `source: 'runtime'`. |
21
+ | `hosted()` | The backend's `GET /init` | One `/init` per page that resolves consent. | Single-page apps and static hosting without a server, where the backend reads each visitor's location from the request. Policy edits apply without a rebuild. |
22
+ | `offline()` | The browser, from rules in your bundle | None. Choices stay in the browser. | Local development, tests and demos. Not recommended for production environments. |
23
23
 
24
24
  `manifest()` is the default in Next.js, TanStack Start, Nuxt, Astro and
25
25
  SvelteKit. React, Vue, Svelte and `@c15t/browser` have no default: pass a
26
- mode yourself. Their quickstarts use `manifest()`. The script tag picks its
26
+ mode yourself. The React, Vue and Svelte quickstarts use `hosted()`, because
27
+ a browser has no request location and a bundled regional policy still needs
28
+ `/init` on a first visit. The JavaScript quickstart uses `manifest()`. The
29
+ script tag picks its
27
30
  mode from the bundle file; see
28
31
  [choose a mode](https://v3.c15t.com/docs/frameworks/html/attributes-and-api#choose-a-mode).
29
32
 
@@ -18,13 +18,13 @@ import {
18
18
  ConsentDialog,
19
19
  ConsentDialogLink,
20
20
  ConsentProvider,
21
- manifest,
21
+ hosted,
22
22
  } from 'c15t/react';
23
23
  import type { ReactNode } from 'react';
24
24
 
25
25
  const options = {
26
- // The policy the build downloaded from VITE_C15T_BACKEND_URL.
27
- mode: manifest(),
26
+ // Asks VITE_C15T_BACKEND_URL for each visitor's policy.
27
+ mode: hosted(),
28
28
  scripts: [
29
29
  posthog({
30
30
  id: 'phc_your_project_key',
@@ -20,13 +20,13 @@ import {
20
20
  ConsentDialog,
21
21
  ConsentDialogLink,
22
22
  ConsentProvider,
23
- manifest,
23
+ hosted,
24
24
  } from 'c15t/react';
25
25
  import type { ReactNode } from 'react';
26
26
 
27
27
  const options = {
28
- // The policy the build downloaded from VITE_C15T_BACKEND_URL.
29
- mode: manifest(),
28
+ // Asks VITE_C15T_BACKEND_URL for each visitor's policy.
29
+ mode: hosted(),
30
30
  scripts: [
31
31
  posthog({
32
32
  id: 'phc_your_project_key',
@@ -20,13 +20,13 @@ import {
20
20
  ConsentDialog,
21
21
  ConsentDialogLink,
22
22
  ConsentProvider,
23
- manifest,
23
+ hosted,
24
24
  } from 'c15t/react';
25
25
  import type { ReactNode } from 'react';
26
26
 
27
27
  const options = {
28
- // The policy the build downloaded from VITE_C15T_BACKEND_URL.
29
- mode: manifest(),
28
+ // Asks VITE_C15T_BACKEND_URL for each visitor's policy.
29
+ mode: hosted(),
30
30
  scripts: [
31
31
  posthog({
32
32
  id: 'phc_your_project_key',
@@ -48,11 +48,14 @@ export const Consent = ({ children }: { children: ReactNode }) => (
48
48
  );
49
49
  ```
50
50
 
51
- `manifest()` from `c15t/react` resolves the policy from the snapshot
52
- `consentManifest()` bundled, and sends consent choices to the backend URL the
53
- plugin read from `VITE_C15T_BACKEND_URL` or `VITE_INTH_PROJECT_URL`. Set one
54
- to the backend URL from your Inth project. To fetch the policy from the backend on every page
55
- load instead, pass `hosted()` from `c15t/react`. The Next.js and TanStack Start adapters export `ConsentRoot` instead, which wraps
51
+ `hosted()` from `c15t/react` asks the backend's `/init` for the visitor's
52
+ policy on each page load, and sends consent choices to the same backend. It
53
+ uses the backend URL `consentManifest()` read from `VITE_C15T_BACKEND_URL` or
54
+ `VITE_INTH_PROJECT_URL`. Set one to the backend URL from your Inth project. To
55
+ resolve a bundled policy in the browser instead, pass `manifest()` from
56
+ `c15t/react`; see
57
+ [rendering](../rendering.md#bundle-the-policy-at-build-time).
58
+ The Next.js and TanStack Start adapters export `ConsentRoot` instead, which wraps
56
59
  this provider and passes it server-resolved state.
57
60
 
58
61
  ## Provider options
@@ -67,7 +70,7 @@ the provider to change them. `scripts`, `vendors`, `networkBlocker`,
67
70
 
68
71
  | Option | Purpose |
69
72
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
- | `mode` | Required. `manifest()` for a bundled policy, `hosted()` for the backend's `/init`, `offline()` for local policies, all from `c15t/react`, or `custom()` for your own transport. See [consent modes](../../../concepts/modes.md) |
73
+ | `mode` | Required. `hosted()` for the backend's `/init`, `manifest()` for a bundled policy, `offline()` for local policies, all from `c15t/react`, or `custom()` for your own transport. See [consent modes](../../../concepts/modes.md) |
71
74
  | `prefetch` | Server-resolved state, or a promise of it, from a framework adapter's server helper |
72
75
  | `scripts` | Consent-gated vendor scripts. See [scripts and embeds](../scripts.md) |
73
76
  | `vendors` | Vendors listed under their category with their own switch. See [vendor consent](../vendor-consent.md) |
@@ -42,13 +42,13 @@ import {
42
42
  ConsentDialog,
43
43
  ConsentDialogLink,
44
44
  ConsentProvider,
45
- manifest,
45
+ hosted,
46
46
  } from 'c15t/react';
47
47
  import type { ReactNode } from 'react';
48
48
 
49
49
  const options = {
50
- // The policy the build downloaded from VITE_C15T_BACKEND_URL.
51
- mode: manifest(),
50
+ // Asks VITE_C15T_BACKEND_URL for each visitor's policy.
51
+ mode: hosted(),
52
52
  scripts: [
53
53
  posthog({
54
54
  id: 'phc_your_project_key',
@@ -16,6 +16,7 @@ A React app with c15t adds these to the page, and your policy must allow them:
16
16
  | `c15t/react/styles.css`, only if you import it with `styles: false` | `style-src 'self'` |
17
17
  | The `<style id="c15t-theme">` element, if you render `ConsentTheme` | `style-src` with a nonce or its hash |
18
18
  | `<script>` elements the script loader creates for `scripts` entries, both `src` and inline | `script-src` with each vendor's host, or a nonce |
19
+ | The inline `<script>` that `consentManifest()` adds to `index.html` to send `/init` early | `script-src` with a nonce or its hash |
19
20
  | With server rendering, the inline `<script>` before the banner's buttons that holds a tap made before hydration | `script-src` with a nonce or its hash |
20
21
  | Browser requests to your backend's `/init` and `/subjects` | `connect-src` with the backend origin |
21
22
  | Iframes inside `ConsentGate`, and the frames, images and requests your vendors load | `frame-src`, `img-src`, `connect-src` for each vendor |
@@ -70,8 +71,9 @@ entry takes precedence for that element. The stock components put it on the
70
71
  With Vite, set `html.cspNonce` to a placeholder in `vite.config.ts`, and have
71
72
  your server replace it with a new nonce on every response. Vite then adds
72
73
  `<meta property="csp-nonce" nonce="...">` to the page, and the browser keeps the
73
- value in the tag's `nonce` property. Read it where you create the provider, in
74
- the quickstart's `src/consent.tsx`:
74
+ value in the tag's `nonce` property. Vite also stamps the nonce on the early
75
+ `/init` script. Read it where you create the provider, in the
76
+ quickstart's `src/consent.tsx`:
75
77
 
76
78
  ```tsx title="src/consent.tsx (partial)"
77
79
  const nonce =
@@ -88,6 +90,10 @@ The provider reads `options.nonce` once, when it mounts. A server-rendered
88
90
  React app, such as one built with React Router framework mode, reads the nonce
89
91
  from its own request and passes it the same way.
90
92
 
93
+ On a static host, allow the early `/init` script by the hash in the browser's
94
+ `Refused to execute inline script` error. If the policy blocks it, the app
95
+ sends `/init` itself.
96
+
91
97
  ## Allow c15t's style elements
92
98
 
93
99
  The banner, dialog and other stock components render c15t's rules as
@@ -67,6 +67,25 @@ your error boundary or the framework's error overlay shows it. Render `IABProvid
67
67
  `c15t/react/iab` for those visitors, or remove the `iab` model from the
68
68
  policy.
69
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
+
70
89
  ## Verify vendor and purpose choices
71
90
 
72
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 build
11
- bundles your project's public policy. The browser loads the page, resolves the
12
- visitor's policy from that bundle, then shows the banner or runs the scripts
13
- the visitor allowed. Regional policies may still need a backend location
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://v3.c15t.com/docs/frameworks). Their adapters resolve consent on the
@@ -28,8 +27,8 @@ 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
- Bundling the policy at build time is the recommended production setup.
32
- Rebuild after changing policies, translations or vendors.
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
 
@@ -43,7 +42,7 @@ Rebuild after changing policies, translations or vendors.
43
42
  `c15t` contains the React adapter at `c15t/react` and the Vite plugin at
44
43
  `c15t/build`. `@c15t/integrations` holds the vendor loaders used below.
45
44
 
46
- ## Bundle your policy
45
+ ## Add the Vite plugin
47
46
 
48
47
  Add `consentManifest` to `vite.config.ts`:
49
48
 
@@ -66,60 +65,14 @@ VITE_C15T_BACKEND_URL=https://your-project.inth.app
66
65
  If your app already sets `VITE_INTH_PROJECT_URL` for other Inth SDKs, that works
67
66
  too. When both are set, `VITE_C15T_BACKEND_URL` wins.
68
67
 
69
- `consentManifest` downloads your project's public policy from `/manifest`
70
- during `vite build`, or in `vite dev` when the app first loads it, and serves
71
- it to your app as `snapshot`
72
- from `c15t/generated`. No file is written into your project.
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 })`.
73
71
 
74
- The snapshot is fixed at build time:
75
-
76
- * Rebuild after changing policies, translations or vendors. If your CI caches
77
- build output, force a fresh build.
78
- * Consent choices still go to the backend.
79
-
80
- The build reads the backend URL from your public backend URL variable when
81
- the config doesn't pass one: `NEXT_PUBLIC_C15T_BACKEND_URL` in Next.js,
82
- `NUXT_PUBLIC_C15T_BACKEND_URL` in Nuxt, `PUBLIC_C15T_BACKEND_URL` in Astro,
83
- Svelte and SvelteKit, and `VITE_C15T_BACKEND_URL` in TanStack Start and other
84
- Vite apps. Each also reads the matching Inth variable, such as
85
- `NEXT_PUBLIC_INTH_PROJECT_URL`, when the c15t one is unset. See
86
- [set the backend URL](../../concepts/modes.md#set-the-backend-url).
87
-
88
- The fetch waits at most 10 seconds. When it fails, or no backend URL is set,
89
- every framework does the same thing:
90
-
91
- | Command | Default when the fetch fails |
92
- | ------------------------------------------------------------------------- | -------------------------------------------------------- |
93
- | Production build: `next build`, `vite build`, `nuxt build`, `astro build` | The build stops with an error. |
94
- | Dev: `next dev`, `vite dev`, `nuxt dev`, `astro dev` | A warning, and the server fetches the policy at runtime. |
95
-
96
- Set `onBuildError` to use one behaviour for both. `'fail'` stops dev too.
97
- `'runtime'` lets a production build finish, and the server fetches the policy
98
- at runtime. The `C15T_ON_BUILD_ERROR` environment variable overrides the
99
- option, so you can deploy during a backend outage without a code change:
100
-
101
- ```sh
102
- C15T_ON_BUILD_ERROR=runtime npm run build
103
- ```
104
-
105
- Turborepo's strict environment mode hides undeclared variables from tasks, so
106
- list `C15T_ON_BUILD_ERROR` in the build task's `passThroughEnv` there.
107
-
108
- The build skips the fetch, without an error, when it can't use a snapshot,
109
- for example when the backend URL is relative. With `onBuildError: 'fail'`, a
110
- relative URL stops the build. [Consent modes](../../concepts/modes.md#what-happens-when-the-download-fails)
111
- lists every case.
112
-
113
- `vite build` and `vite dev` fetch the manifest when they start. `vite preview`
114
- serves the last build without fetching. The plugin writes no file into your
115
- app, so there is nothing to keep out of Git. `c15t/generated` ships its own
116
- types, so `tsc`, `vue-tsc` and `svelte-check` pass on a fresh checkout without
117
- a build first.
118
-
119
- The plugin can't see the options your app passes to `manifest()`. When the
120
- app passes `source: 'runtime'` or `manifestURL`, set
121
- `consentManifest({ source: 'runtime' })` too. The build then downloads no
122
- manifest, so it doesn't fail when the backend's `/manifest` is down.
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.
123
76
 
124
77
  ## Mount the provider
125
78
 
@@ -135,13 +88,13 @@ import {
135
88
  ConsentDialog,
136
89
  ConsentDialogLink,
137
90
  ConsentProvider,
138
- manifest,
91
+ hosted,
139
92
  } from 'c15t/react';
140
93
  import type { ReactNode } from 'react';
141
94
 
142
95
  const options = {
143
- // The policy the build downloaded from VITE_C15T_BACKEND_URL.
144
- mode: manifest(),
96
+ // Asks VITE_C15T_BACKEND_URL for each visitor's policy.
97
+ mode: hosted(),
145
98
  scripts: [
146
99
  posthog({
147
100
  id: 'phc_your_project_key',
@@ -163,18 +116,11 @@ export const Consent = ({ children }: { children: ReactNode }) => (
163
116
  );
164
117
  ```
165
118
 
166
- `manifest()` resolves the policy from the bundled snapshot in the browser,
167
- with no `/manifest` request. It reads the snapshot and the backend URL from
168
- what `consentManifest` downloaded. Consent choices go to that backend, and so
169
- does an `/init` request when the policy depends on the visitor's country or
170
- region and the browser does not know it. That request returns the backend's
171
- current policy, which can be newer than the snapshot. Vite reads
172
- `VITE_C15T_BACKEND_URL` at build time, so set it before you build, not when
173
- you serve the files.
174
-
175
- To apply policy edits without rebuilding, use `hosted()` from `c15t/react` as
176
- the mode. The browser then requests the policy from `/init` on every page load.
177
- [Consent modes](../../concepts/modes.md) compares the three modes.
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.
178
124
 
179
125
  PostHog waits for measurement permission, so your policy must include that
180
126
  category. Replace `phc_your_project_key` with your PostHog project key.
@@ -234,7 +180,8 @@ Build and preview the production bundle with `vite build` and `vite preview`,
234
180
  then open the site in a private window with DevTools open on the Network tab.
235
181
  Test under a policy that asks for a choice, such as an EU opt-in policy.
236
182
 
237
- 1. **Before a choice.** The banner shows, with no `/manifest` request. There
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
238
185
  are no requests to `posthog.com`.
239
186
  2. **Reject All.** The banner closes and the vendor requests stay absent.
240
187
  3. **Reload.** The banner stays closed and the vendor requests stay absent.
@@ -243,8 +190,7 @@ Test under a policy that asks for a choice, such as an EU opt-in policy.
243
190
  save. PostHog's `array.js` loads.
244
191
  5. **Turn Analytics off again.** The page reloads and PostHog does not load.
245
192
 
246
- If the banner never appears, check the generated policy and any location
247
- 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)
248
194
  covers the common causes. [Verify consent](../../guides/verify-consent.md) has the
249
195
  full release checklist.
250
196