@c15t/react 3.0.0-alpha.7 → 3.0.0-alpha.9
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 +3 -3
- package/dist/components/consent-gate/atoms.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/panel/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/root.js +1 -1
- package/dist/components/preferences/atoms/root.js +1 -1
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/prompt/prompt.js +1 -1
- package/dist/components/shared/dialog-sheets.js +1 -0
- package/dist/components/shared/first-paint-sheets.js +1 -0
- package/dist/components/shared/iab-dialog-sheets.js +1 -0
- package/dist/components/shared/iab-first-paint-sheets.js +1 -0
- package/dist/components/shared/primitives/legal-links/index.js +1 -1
- package/dist/components/shared/streamed-surface.js +2 -0
- package/dist/components/shared/surface-styles.js +2 -0
- package/dist/context.js +1 -1
- package/dist/hooks/use-iab-policy-guard.js +1 -0
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +1 -1
- package/dist/iab-context.js +1 -1
- package/dist/kernel-selector.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/components/prompt/atoms/root.d.ts +4 -0
- package/dist-types/components/prompt/prompt.d.ts +4 -0
- package/dist-types/components/shared/dialog-sheets.d.ts +9 -0
- package/dist-types/components/shared/first-paint-sheets.d.ts +8 -0
- package/dist-types/components/shared/iab-dialog-sheets.d.ts +3 -0
- package/dist-types/components/shared/iab-first-paint-sheets.d.ts +3 -0
- package/dist-types/components/shared/streamed-surface.d.ts +23 -0
- package/dist-types/components/shared/surface-styles.d.ts +36 -0
- package/dist-types/context.d.ts +17 -1
- package/dist-types/hooks/use-iab-policy-guard.d.ts +35 -0
- package/dist-types/kernel-selector.d.ts +10 -0
- package/dist-types/provider.d.ts +36 -4
- package/dist-types/ui-config-context.d.ts +4 -0
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +3 -3
- package/docs/concepts/choose-your-setup.md +4 -2
- package/docs/concepts/data-fetching.md +3 -1
- package/docs/customization/overview.md +3 -2
- package/docs/customization/stylesheets.md +163 -50
- package/docs/customization/tailwind.md +59 -44
- package/docs/customization/tokens.md +6 -6
- package/docs/frameworks/react/components/consent-banner.md +7 -4
- package/docs/frameworks/react/components/consent-dialog.md +8 -5
- package/docs/frameworks/react/components/consent-provider.md +41 -36
- package/docs/frameworks/react/components.md +9 -5
- package/docs/frameworks/react/compose.md +3 -3
- package/docs/frameworks/react/content-security-policy.md +22 -8
- package/docs/frameworks/react/customize.md +21 -13
- package/docs/frameworks/react/iab.md +36 -4
- package/docs/frameworks/react/quickstart.md +84 -85
- package/docs/frameworks/react/rendering.md +24 -12
- package/docs/frameworks/react/scripts.md +7 -4
- package/docs/frameworks/react/troubleshooting.md +70 -19
- package/docs/frameworks/react/upgrade-v3.md +23 -17
- package/docs/frameworks/react/vendor-consent.md +19 -7
- package/docs/guides/banner-experiments.md +2 -4
- package/docs/guides/troubleshooting.md +9 -9
- package/package.json +7 -7
|
@@ -21,10 +21,11 @@ Install `@c15t/iab` alongside your framework adapter, then mount the IAB
|
|
|
21
21
|
components inside an existing consent provider connected to Inth.
|
|
22
22
|
|
|
23
23
|
Import the components from `c15t/react/iab` and render them inside your
|
|
24
|
-
existing `ConsentProvider` from `c15t/react`.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
existing `ConsentProvider` from `c15t/react`.
|
|
25
|
+
|
|
26
|
+
The IAB banner and dialog render their own styles by default. Leave the
|
|
27
|
+
provider's `styles` option enabled and render the components without a CSS
|
|
28
|
+
import:
|
|
28
29
|
|
|
29
30
|
```tsx title="src/iab-consent.tsx"
|
|
30
31
|
import {
|
|
@@ -55,6 +56,18 @@ IAB presentation follows `presentation.prompt.blocking` and
|
|
|
55
56
|
scroll locking and a backdrop together. See [migration](../../upgrade-v3.md) for
|
|
56
57
|
legacy option behavior.
|
|
57
58
|
|
|
59
|
+
## Mount IABProvider for every IAB policy
|
|
60
|
+
|
|
61
|
+
IAB is opt-in. The standard `ConsentBanner` and `ConsentDialog` do not
|
|
62
|
+
handle the IAB model, so when a visitor's policy uses it, the backend
|
|
63
|
+
sends its vendor list and the app has no `IABProvider`, they throw an
|
|
64
|
+
`IABUnavailableError` (code `C15T_IAB_UNAVAILABLE`):
|
|
65
|
+
`c15t: this visitor's policy uses IAB TCF, but no <IABProvider> is mounted`.
|
|
66
|
+
The error happens during render, on the server and in the browser, so
|
|
67
|
+
your error boundary or the framework's error overlay shows it. Render `IABProvider` from
|
|
68
|
+
`c15t/react/iab` for those visitors, or remove the `iab` model from the
|
|
69
|
+
policy.
|
|
70
|
+
|
|
58
71
|
## Verify vendor and purpose choices
|
|
59
72
|
|
|
60
73
|
Test category choices alongside vendor, purpose, legitimate-interest and special
|
|
@@ -62,6 +75,25 @@ feature controls used by your integration. Confirm the TC String and `__tcfapi`
|
|
|
62
75
|
reflect the saved state, and verify the vendor's actual requests. A category-only
|
|
63
76
|
banner test does not establish that the IAB flow works.
|
|
64
77
|
|
|
78
|
+
## Load an external stylesheet
|
|
79
|
+
|
|
80
|
+
With Tailwind CSS 3, or when you want to put c15t in a named cascade layer,
|
|
81
|
+
import `c15t/react/styles.css` and then `c15t/react/iab/styles.css` at your
|
|
82
|
+
global stylesheet entry. The IAB stylesheet holds the IAB rules and uses the
|
|
83
|
+
base stylesheet's tokens. Set `styles: false` so the components use those
|
|
84
|
+
stylesheets:
|
|
85
|
+
|
|
86
|
+
```tsx title="src/consent.tsx (partial)"
|
|
87
|
+
import 'c15t/react/styles.css';
|
|
88
|
+
import 'c15t/react/iab/styles.css';
|
|
89
|
+
|
|
90
|
+
// In the ConsentProvider options:
|
|
91
|
+
styles: false,
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Tailwind CSS 3 also needs the
|
|
95
|
+
[PostCSS plugin](../../customization/tailwind.md#set-up-tailwind-css-3).
|
|
96
|
+
|
|
65
97
|
## Restrict how vendors use a purpose
|
|
66
98
|
|
|
67
99
|
Publisher restrictions override a vendor's declared use of one purpose. Pass
|
|
@@ -7,10 +7,11 @@ 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
|
-
|
|
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.
|
|
14
15
|
|
|
15
16
|
* **Next.js or TanStack Start?** Use that framework's guide from the
|
|
16
17
|
[framework list](https://c15t.com/docs/frameworks). Their adapters resolve consent on the
|
|
@@ -27,21 +28,67 @@ the backend URL.
|
|
|
27
28
|
[Choose your setup](../../concepts/choose-your-setup.md#decide-who-runs-the-consent-backend)
|
|
28
29
|
covers self-hosting and offline mode.
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
deploying, and rebuild after changing policies, translations or vendors.
|
|
31
|
+
Bundling the policy at build time is the recommended production setup.
|
|
32
|
+
Rebuild after changing policies, translations or vendors.
|
|
33
33
|
|
|
34
34
|
## Install c15t
|
|
35
35
|
|
|
36
|
-
| Package manager | Command
|
|
37
|
-
| :-------------- |
|
|
38
|
-
| npm | `npm install c15t@alpha @c15t/integrations@alpha` |
|
|
39
|
-
| pnpm | `pnpm add c15t@alpha @c15t/integrations@alpha` |
|
|
40
|
-
| yarn | `yarn add c15t@alpha @c15t/integrations@alpha` |
|
|
41
|
-
| bun | `bun add c15t@alpha @c15t/integrations@alpha` |
|
|
36
|
+
| Package manager | Command |
|
|
37
|
+
| :-------------- | :-------------------------------------------------------------------- |
|
|
38
|
+
| npm | `npm install c15t@alpha @c15t/browser@alpha @c15t/integrations@alpha` |
|
|
39
|
+
| pnpm | `pnpm add c15t@alpha @c15t/browser@alpha @c15t/integrations@alpha` |
|
|
40
|
+
| yarn | `yarn add c15t@alpha @c15t/browser@alpha @c15t/integrations@alpha` |
|
|
41
|
+
| bun | `bun add c15t@alpha @c15t/browser@alpha @c15t/integrations@alpha` |
|
|
42
42
|
|
|
43
|
-
`c15t` contains the React adapter at `c15t/react
|
|
44
|
-
|
|
43
|
+
`c15t` contains the React adapter at `c15t/react` and the Vite plugin at
|
|
44
|
+
`c15t/build`. `@c15t/browser` resolves the bundled policy in the browser.
|
|
45
|
+
`@c15t/integrations` holds the vendor loaders used below.
|
|
46
|
+
|
|
47
|
+
## Bundle your policy
|
|
48
|
+
|
|
49
|
+
Add `consentManifest` to `vite.config.ts`:
|
|
50
|
+
|
|
51
|
+
```ts title="vite.config.ts"
|
|
52
|
+
import react from '@vitejs/plugin-react';
|
|
53
|
+
import { consentManifest } from 'c15t/build';
|
|
54
|
+
import { defineConfig, loadEnv } from 'vite';
|
|
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.
|
|
45
92
|
|
|
46
93
|
## Register your vendor scripts
|
|
47
94
|
|
|
@@ -77,20 +124,23 @@ asks for them, and `ConsentDialogLink` keeps preferences reachable after the
|
|
|
77
124
|
banner closes.
|
|
78
125
|
|
|
79
126
|
```tsx title="src/consent.tsx"
|
|
127
|
+
import { manifest } from '@c15t/browser/headless';
|
|
80
128
|
import {
|
|
81
129
|
ConsentBanner,
|
|
82
130
|
ConsentDialog,
|
|
83
131
|
ConsentDialogLink,
|
|
84
132
|
ConsentProvider,
|
|
85
|
-
hosted,
|
|
86
133
|
} from 'c15t/react';
|
|
87
134
|
import type { ReactNode } from 'react';
|
|
88
135
|
|
|
136
|
+
import { consentManifest } from './c15t-manifest';
|
|
89
137
|
import { scripts } from './scripts';
|
|
90
138
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
139
|
+
const mode = manifest({
|
|
140
|
+
backendURL:
|
|
141
|
+
import.meta.env.VITE_C15T_BACKEND_URL ?? 'https://benchmarks-inth.inth.app',
|
|
142
|
+
manifest: consentManifest,
|
|
143
|
+
});
|
|
94
144
|
|
|
95
145
|
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
96
146
|
<ConsentProvider options={{ mode, scripts }}>
|
|
@@ -104,8 +154,17 @@ export const Consent = ({ children }: { children: ReactNode }) => (
|
|
|
104
154
|
);
|
|
105
155
|
```
|
|
106
156
|
|
|
107
|
-
|
|
108
|
-
|
|
157
|
+
`manifest()` resolves the policy from the bundled snapshot in the browser,
|
|
158
|
+
with no `/manifest` request. Use the same backend URL as in
|
|
159
|
+
`vite.config.ts`: consent choices go to it, and so does an `/init` request
|
|
160
|
+
when the policy depends on the visitor's country or region and the browser
|
|
161
|
+
does not know it. That request returns the backend's current policy, which can
|
|
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.
|
|
164
|
+
|
|
165
|
+
To apply policy edits without rebuilding, remove `consentManifest` and use
|
|
166
|
+
`hosted({ url })` from `c15t/react` as the mode. The browser then requests the
|
|
167
|
+
policy from `/init` on every page load.
|
|
109
168
|
|
|
110
169
|
Wrap your app with it in `src/main.tsx`:
|
|
111
170
|
|
|
@@ -128,68 +187,8 @@ createRoot(root).render(
|
|
|
128
187
|
|
|
129
188
|
Keep `Consent` mounted for the life of the app. If you use a client-side
|
|
130
189
|
router, render the router inside `Consent` so navigation does not remount the
|
|
131
|
-
provider.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
## Bundle the manifest during builds
|
|
135
|
-
|
|
136
|
-
The build fetches your public policy once and bundles it, so the server never
|
|
137
|
-
fetches it at runtime.
|
|
138
|
-
|
|
139
|
-
Add `consentManifest` to your existing Vite plugin list:
|
|
140
|
-
|
|
141
|
-
```ts title="vite.config.ts (plugins option)"
|
|
142
|
-
import { consentManifest } from 'c15t/build';
|
|
143
|
-
|
|
144
|
-
export default {
|
|
145
|
-
plugins: [
|
|
146
|
-
// Keep your existing framework plugins here.
|
|
147
|
-
consentManifest({ backendURL: 'https://your-project.inth.app' }),
|
|
148
|
-
],
|
|
149
|
-
};
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Vite writes `src/c15t-manifest.ts` before compiling, including at development
|
|
153
|
-
startup. Keep the generated file out of Git. Install `@c15t/browser@alpha` for
|
|
154
|
-
its browser manifest transport. In the quickstart's `src/consent.tsx`, replace
|
|
155
|
-
the `hosted()` mode with this factory:
|
|
156
|
-
|
|
157
|
-
```tsx title="src/consent.tsx"
|
|
158
|
-
import { manifest } from '@c15t/browser/headless';
|
|
159
|
-
|
|
160
|
-
import { consentManifest } from './c15t-manifest';
|
|
161
|
-
|
|
162
|
-
const mode = manifest({
|
|
163
|
-
backendURL: 'https://your-project.inth.app',
|
|
164
|
-
manifest: consentManifest,
|
|
165
|
-
});
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Keep passing `mode` to `ConsentProvider`. The browser resolves the snapshot
|
|
169
|
-
without `/manifest` when it has the geography the policy needs, or the policy
|
|
170
|
-
does not depend on geography. Supply known country and region through `inputs`
|
|
171
|
-
or overrides to keep regional resolution on the snapshot.
|
|
172
|
-
|
|
173
|
-
When the required country or region is missing, the transport calls backend
|
|
174
|
-
`/init` and uses its resolved policy and translations. That response can use
|
|
175
|
-
newer backend policy than the build snapshot. This is a full consent resolution,
|
|
176
|
-
so these visitors still wait for a backend round trip.
|
|
177
|
-
|
|
178
|
-
Policy and resolver code are part of the browser bundle.
|
|
179
|
-
|
|
180
|
-
The snapshot is fixed at build time:
|
|
181
|
-
|
|
182
|
-
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
183
|
-
build output, force a fresh build.
|
|
184
|
-
* The build must reach the backend. A failed fetch fails the build.
|
|
185
|
-
* Consent choices still go to the backend.
|
|
186
|
-
|
|
187
|
-
Run `vite build` or `vite dev` before a standalone `tsc`, `vue-tsc` or
|
|
188
|
-
`svelte-check`; the generated file doesn't exist on a fresh checkout.
|
|
189
|
-
|
|
190
|
-
For policy updates without a rebuild, keep the original `hosted()` factory
|
|
191
|
-
and omit the build plugin. That setup resolves policy through the backend
|
|
192
|
-
`/init` on each page load.
|
|
190
|
+
provider. There is no stylesheet to import: the banner and dialog render their
|
|
191
|
+
own rules as `<style>` elements.
|
|
193
192
|
|
|
194
193
|
## Gate your own features
|
|
195
194
|
|
|
@@ -215,8 +214,8 @@ Build and preview the production bundle with `vite build` and `vite preview`,
|
|
|
215
214
|
then open the site in a private window with DevTools open on the Network tab.
|
|
216
215
|
Test under a policy that asks for a choice, such as an EU opt-in policy.
|
|
217
216
|
|
|
218
|
-
1. **Before a choice.** The banner shows
|
|
219
|
-
`posthog.com`.
|
|
217
|
+
1. **Before a choice.** The banner shows, with no `/manifest` request. There
|
|
218
|
+
are no requests to `posthog.com`.
|
|
220
219
|
2. **Reject All.** The banner closes and the vendor requests stay absent.
|
|
221
220
|
3. **Reload.** The banner stays closed and the vendor requests stay absent.
|
|
222
221
|
4. **Privacy settings.** The footer link opens the dialog with **Analytics** (the
|
|
@@ -25,6 +25,9 @@ explains the difference in general terms.
|
|
|
25
25
|
|
|
26
26
|
## Bundle the manifest during Vite builds
|
|
27
27
|
|
|
28
|
+
The [quickstart](./quickstart.md) and `examples/react` in
|
|
29
|
+
the c15t repository use this setup.
|
|
30
|
+
|
|
28
31
|
The build fetches your public policy once and bundles it, so the server never
|
|
29
32
|
fetches it at runtime.
|
|
30
33
|
|
|
@@ -43,8 +46,8 @@ export default {
|
|
|
43
46
|
|
|
44
47
|
Vite writes `src/c15t-manifest.ts` before compiling, including at development
|
|
45
48
|
startup. Keep the generated file out of Git. Install `@c15t/browser@alpha` for
|
|
46
|
-
its browser manifest transport. In
|
|
47
|
-
|
|
49
|
+
its browser manifest transport. In `src/consent.tsx`, create the provider's
|
|
50
|
+
mode with `manifest()` instead of `hosted()`:
|
|
48
51
|
|
|
49
52
|
```tsx title="src/consent.tsx"
|
|
50
53
|
import { manifest } from '@c15t/browser/headless';
|
|
@@ -73,23 +76,27 @@ The snapshot is fixed at build time:
|
|
|
73
76
|
|
|
74
77
|
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
75
78
|
build output, force a fresh build.
|
|
76
|
-
* The build must reach the backend. A failed fetch
|
|
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.
|
|
77
82
|
* Consent choices still go to the backend.
|
|
78
83
|
|
|
79
84
|
Run `vite build` or `vite dev` before a standalone `tsc`, `vue-tsc` or
|
|
80
|
-
`svelte-check`; the generated file doesn't exist on a fresh checkout.
|
|
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.
|
|
81
88
|
|
|
82
|
-
For policy updates without a rebuild,
|
|
83
|
-
and omit the build plugin. That setup resolves policy through the
|
|
84
|
-
`/init` on each page load.
|
|
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.
|
|
85
92
|
|
|
86
93
|
## Client-rendered apps
|
|
87
94
|
|
|
88
95
|
The recommended [quickstart](./quickstart.md) bundles the
|
|
89
96
|
manifest and resolves it in the browser. See [build-time manifests](#bundle-the-manifest-during-vite-builds).
|
|
90
97
|
|
|
91
|
-
For policy updates without rebuilding,
|
|
92
|
-
path works as follows:
|
|
98
|
+
For policy updates without rebuilding, use `hosted({ url })` from
|
|
99
|
+
`c15t/react` as the mode. That runtime path works as follows:
|
|
93
100
|
|
|
94
101
|
1. The page loads with no consent state. Every optional category is denied.
|
|
95
102
|
2. `ConsentProvider` requests `GET /init` from your backend URL while it first
|
|
@@ -120,8 +127,8 @@ browser takes over after hydration:
|
|
|
120
127
|
* The server render and the first browser render agree, so hydration does not
|
|
121
128
|
warn or change a stored choice.
|
|
122
129
|
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
The Vite setup needs no change. The banner renders in the browser and brings
|
|
131
|
+
its own `<style>` element, so there is no stylesheet to import.
|
|
125
132
|
|
|
126
133
|
### Skip the browser's first init request
|
|
127
134
|
|
|
@@ -182,7 +189,12 @@ With `hosted({ url })`, the browser calls two backend endpoints:
|
|
|
182
189
|
| `GET {url}/init` | Once per page load, to resolve the policy |
|
|
183
190
|
| `POST {url}/subjects` | When the visitor accepts, rejects or saves |
|
|
184
191
|
|
|
185
|
-
|
|
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
|
+
These requests go to the backend's origin, so it must list your site as a
|
|
197
|
+
trusted origin.
|
|
186
198
|
[Data fetching](../../concepts/data-fetching.md) compares this with proxies and
|
|
187
199
|
custom transports.
|
|
188
200
|
|
|
@@ -28,20 +28,23 @@ export const scripts = [
|
|
|
28
28
|
The provider in `src/consent.tsx` receives them:
|
|
29
29
|
|
|
30
30
|
```tsx title="src/consent.tsx"
|
|
31
|
+
import { manifest } from '@c15t/browser/headless';
|
|
31
32
|
import {
|
|
32
33
|
ConsentBanner,
|
|
33
34
|
ConsentDialog,
|
|
34
35
|
ConsentDialogLink,
|
|
35
36
|
ConsentProvider,
|
|
36
|
-
hosted,
|
|
37
37
|
} from 'c15t/react';
|
|
38
38
|
import type { ReactNode } from 'react';
|
|
39
39
|
|
|
40
|
+
import { consentManifest } from './c15t-manifest';
|
|
40
41
|
import { scripts } from './scripts';
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const mode = manifest({
|
|
44
|
+
backendURL:
|
|
45
|
+
import.meta.env.VITE_C15T_BACKEND_URL ?? 'https://benchmarks-inth.inth.app',
|
|
46
|
+
manifest: consentManifest,
|
|
47
|
+
});
|
|
45
48
|
|
|
46
49
|
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
47
50
|
<ConsentProvider options={{ mode, scripts }}>
|
|
@@ -5,30 +5,67 @@ description: Fix a missing banner, early vendor requests, unstyled components
|
|
|
5
5
|
group: frameworks
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Why does the build fail to download the manifest?
|
|
9
9
|
|
|
10
|
-
**Check.**
|
|
11
|
-
`
|
|
10
|
+
**Check.** `vite build` or `vite dev` stops before compiling, with an error
|
|
11
|
+
from `consentManifest` that starts with `@c15t/core/build:`.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
| Error | Cause | Fix |
|
|
14
|
+
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
15
|
+
| `build-time manifests require an absolute upstream URL.` | `backendURL` is empty or relative, such as `/api/c15t` | Pass the absolute backend URL from your Inth project |
|
|
16
|
+
| `build-time manifests require an http(s) upstream URL.` | `backendURL` uses another protocol | Use the `https://` URL Inth shows |
|
|
17
|
+
| `/manifest responded 404` | The URL is not your project's backend, such as the `https://your-project.inth.app` placeholder or a URL missing its path prefix | Copy the backend URL exactly as Inth shows it, or set `VITE_C15T_BACKEND_URL` |
|
|
18
|
+
| `/manifest returned an invalid consent manifest.` | The URL answered with something other than a consent manifest, such as an HTML page | Point `backendURL` at the backend itself, not your site or a dashboard page |
|
|
19
|
+
| `fetch failed` | The build cannot reach the backend | Build where the backend is reachable. A failed download never reuses an old snapshot |
|
|
20
|
+
|
|
21
|
+
## Why can't TypeScript find ./c15t-manifest?
|
|
22
|
+
|
|
23
|
+
**Check.** `tsc`, `vue-tsc` or your editor reports that it cannot find the
|
|
24
|
+
module `./c15t-manifest`, on a fresh checkout or in CI.
|
|
25
|
+
|
|
26
|
+
**Fix.** `consentManifest` writes `src/c15t-manifest.ts` when `vite build` or
|
|
27
|
+
`vite dev` starts, and the file is not in Git. Run one of them before the type
|
|
28
|
+
check. In CI, run the build first, or cache the generated file with the build
|
|
29
|
+
output.
|
|
16
30
|
|
|
17
31
|
## There is no banner
|
|
18
32
|
|
|
19
33
|
**Check.** In DevTools Network, find the `GET` request to `/init` on your
|
|
20
|
-
backend URL.
|
|
34
|
+
backend URL. With the bundled manifest, the browser makes no request before
|
|
35
|
+
the banner unless the policy depends on a country or region it does not know.
|
|
21
36
|
|
|
22
|
-
| What you see | Cause
|
|
23
|
-
| ------------------------------- |
|
|
24
|
-
| No
|
|
25
|
-
| `/init` fails with a CORS error | Your site's origin is not trusted
|
|
26
|
-
| `/init` returns 200 | The policy for this visitor asks for no banner, or a stored choice is still valid
|
|
37
|
+
| What you see | Cause | Fix |
|
|
38
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| No request and no banner | `ConsentProvider` is not mounted, an error stopped `src/consent.tsx` from rendering, or the bundled policy asks for no banner | Check the console for errors, render one provider at the app root as in the [quickstart](./quickstart.md), and inspect the active policy with the component below |
|
|
40
|
+
| `/init` fails with a CORS error | Your site's origin is not trusted | Add the exact origin, including port in development, to the trusted origins in your Inth project |
|
|
41
|
+
| `/init` returns 200 | The policy for this visitor asks for no banner, or a stored choice is still valid | Inspect the active policy with the component below, or clear site data |
|
|
27
42
|
|
|
28
43
|
A policy with `prompt: 'none'` shows no banner by design, and so does a
|
|
29
44
|
returning visitor with a valid stored choice. [Policies](../../concepts/policies.md)
|
|
30
45
|
explains why a region may not show one.
|
|
31
46
|
|
|
47
|
+
## The page throws that no IABProvider is mounted
|
|
48
|
+
|
|
49
|
+
The visitor's policy uses the `iab` model and the backend sent its vendor
|
|
50
|
+
list, but no `IABProvider` is mounted. The standard banner and dialog do
|
|
51
|
+
not handle the IAB model, so they throw rather than leave the visitor
|
|
52
|
+
with no consent UI. Server and browser render the same error.
|
|
53
|
+
|
|
54
|
+
Fix it one of two ways:
|
|
55
|
+
|
|
56
|
+
* If the site uses IAB TCF, render `IABProvider` with `IABConsentBanner`
|
|
57
|
+
and `IABConsentDialog` from `c15t/react/iab` inside your consent
|
|
58
|
+
provider. They can sit next to the standard banner.
|
|
59
|
+
* If it does not, remove the `iab` model from the policy for that region
|
|
60
|
+
in your Inth project or policy pack.
|
|
61
|
+
|
|
62
|
+
A backend that answers `gvl: null` turns IAB off for that request. The
|
|
63
|
+
policy then runs as opt-in and nothing throws.
|
|
64
|
+
|
|
65
|
+
The check passes once `c15t/react/iab` has loaded. If you import the IAB
|
|
66
|
+
components with a dynamic `import()`, a render of the standard banner
|
|
67
|
+
that happens before the import finishes still throws.
|
|
68
|
+
|
|
32
69
|
## Inspect the active policy
|
|
33
70
|
|
|
34
71
|
Render this component inside `ConsentProvider`. It reads state and records no
|
|
@@ -76,13 +113,27 @@ covers the options.
|
|
|
76
113
|
## The banner has no styles
|
|
77
114
|
|
|
78
115
|
**Check.** In DevTools Elements, the banner has classes but no rules apply.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
116
|
+
Look for c15t's `<style>` element: `<style data-href="c15t-first-paint">` in
|
|
117
|
+
`<head>` on React 19, or `<style data-c15t-styles>` next to the banner on
|
|
118
|
+
React 18 or with a `nonce`. Check the console for a Content Security Policy
|
|
119
|
+
error.
|
|
120
|
+
|
|
121
|
+
**Fix.**
|
|
122
|
+
|
|
123
|
+
* No c15t `<style>` element: the provider sets `styles: false` or `noStyle`.
|
|
124
|
+
With `styles: false`, import `c15t/react/styles.css` once in the module
|
|
125
|
+
that renders the provider.
|
|
126
|
+
* The console reports a blocked inline style: set the provider's `nonce`, as
|
|
127
|
+
[Content Security Policy](./content-security-policy.md)
|
|
128
|
+
shows.
|
|
129
|
+
* Under Tailwind 3, buttons lose their padding and cards their borders:
|
|
130
|
+
Tailwind 3's unlayered preflight beats c15t's layered rules. Set
|
|
131
|
+
`styles: false`, import `c15t/react/styles.css` above the `@tailwind`
|
|
132
|
+
directives and add `c15t/postcss-tailwind3` before `tailwindcss` in your
|
|
133
|
+
PostCSS config. Without the plugin, Tailwind 3 drops c15t's rules or fails
|
|
134
|
+
the build with
|
|
135
|
+
`` `@layer components` is used but no matching `@tailwind components` directive is present ``.
|
|
136
|
+
See [Tailwind CSS](../../customization/tailwind.md#set-up-tailwind-css-3).
|
|
86
137
|
|
|
87
138
|
## The console warns that theme tokens are no longer turned into CSS
|
|
88
139
|
|
|
@@ -51,13 +51,13 @@ Remove `@c15t/react` and `@c15t/scripts`, then install `c15t` from the
|
|
|
51
51
|
|
|
52
52
|
Drop `@c15t/integrations@alpha` if you don't use its vendor helpers.
|
|
53
53
|
|
|
54
|
-
| v2 import | v3 import
|
|
55
|
-
| --------------------------------------- |
|
|
56
|
-
| `@c15t/react` | `c15t/react`
|
|
57
|
-
| `@c15t/react/headless` | `c15t/react/headless`
|
|
58
|
-
| `@c15t/react/styles.css` | `c15t/react/styles.css`
|
|
59
|
-
| `DevTools` from `@c15t/dev-tools/react` | `DevTools` from `c15t/react/devtools`
|
|
60
|
-
| `@c15t/scripts/*` | `@c15t/integrations/*`
|
|
54
|
+
| v2 import | v3 import |
|
|
55
|
+
| --------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
56
|
+
| `@c15t/react` | `c15t/react` |
|
|
57
|
+
| `@c15t/react/headless` | `c15t/react/headless` |
|
|
58
|
+
| `@c15t/react/styles.css` | Nothing: remove the import. Keep `c15t/react/styles.css` only with `styles: false` |
|
|
59
|
+
| `DevTools` from `@c15t/dev-tools/react` | `DevTools` from `c15t/react/devtools` |
|
|
60
|
+
| `@c15t/scripts/*` | `@c15t/integrations/*` |
|
|
61
61
|
|
|
62
62
|
`@c15t/react` is still published at v3, so its imports keep working if you pin
|
|
63
63
|
it to `@alpha`. These docs use `c15t/react`.
|
|
@@ -448,16 +448,22 @@ A generated theme uses `:root:root`, so your own `--c15t-*` values on plain
|
|
|
448
448
|
|
|
449
449
|
### Stylesheets
|
|
450
450
|
|
|
451
|
-
*
|
|
452
|
-
`@c15t/
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
*
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
451
|
+
* Remove the c15t stylesheet import, such as `@c15t/react/styles.css` or
|
|
452
|
+
`@c15t/nextjs/styles.css`. The stock components in `c15t/react`,
|
|
453
|
+
`c15t/next` and `c15t/tanstack-start` render the rules they use as
|
|
454
|
+
`<style>` elements, so no stylesheet request holds back the first paint. An
|
|
455
|
+
import you keep still styles the components, but the rules ship twice and
|
|
456
|
+
the stylesheet still delays the first paint.
|
|
457
|
+
* Keep an import of the v3 stylesheet, such as `c15t/react/styles.css`, only
|
|
458
|
+
with Tailwind CSS 3 or a named cascade layer, and set
|
|
459
|
+
`styles: false` in the provider options so the components add no second
|
|
460
|
+
copy. See
|
|
461
|
+
[stylesheets and CSS layers](../../customization/stylesheets.md#import-the-stylesheet-yourself).
|
|
462
|
+
* With manual styles, `iab/styles.css` no longer repeats the default tokens.
|
|
463
|
+
Import it after `styles.css`. Automatic IAB styling needs neither import.
|
|
464
|
+
* Tailwind CSS 3 needs a PostCSS plugin for c15t's `styles.css`. Add
|
|
465
|
+
`'c15t/postcss-tailwind3': {}` before `tailwindcss` in `postcss.config`,
|
|
466
|
+
and set `styles: false`. See [Tailwind CSS 3](../../customization/tailwind.md#set-up-tailwind-css-3).
|
|
461
467
|
Create React App can't run the plugin. The `postcss-tailwind3` codemod adds
|
|
462
468
|
it to an object-form `postcss.config` and warns about configs it can't
|
|
463
469
|
edit.
|
|
@@ -22,6 +22,10 @@ TC string instead; see [IAB TCF](./iab.md).
|
|
|
22
22
|
|
|
23
23
|
## Declare the vendors
|
|
24
24
|
|
|
25
|
+
Built-in `@c15t/integrations` helpers already list their vendor by name, so
|
|
26
|
+
you only declare a vendor for a script of your own, or to replace a helper's
|
|
27
|
+
name and privacy policy.
|
|
28
|
+
|
|
25
29
|
Declare the vendors next to the scripts in `src/scripts.ts`, using the `vendor`
|
|
26
30
|
slug each script already carries:
|
|
27
31
|
|
|
@@ -66,11 +70,19 @@ helper sets `vendor` to its script ID, so `xPixel()` is `x-pixel`, `gtag()`
|
|
|
66
70
|
is `gtag` and `cloudflareZaraz()` is `cloudflare-zaraz`. Each
|
|
67
71
|
[vendor guide](../../integrations/overview.md) names its slug.
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
You don't need to declare a helper's vendor. Each helper also sets
|
|
74
|
+
`vendorDetails` on its script: the vendor's name, privacy policy, homepage
|
|
75
|
+
and legal entity. The dialog lists the vendor with its own switch from
|
|
76
|
+
those. Declare the vendor in `vendors` to replace them, for example to link
|
|
77
|
+
your own data processing notice, or when you self-host a tool such as
|
|
78
|
+
Matomo, Umami, Plausible or PostHog and the vendor's privacy policy doesn't
|
|
79
|
+
cover your install. A declaration replaces `vendorDetails` as a whole and
|
|
80
|
+
doesn't merge with it.
|
|
81
|
+
|
|
82
|
+
Your own scripts can set `vendorDetails` too. A script with a `vendor` slug
|
|
83
|
+
and no `name` and `privacyPolicyUrl` from any source still loads with its
|
|
84
|
+
category, but the dialog has no switch for it. In development, c15t logs a
|
|
85
|
+
console warning that names the slug. Production builds skip the warning.
|
|
74
86
|
|
|
75
87
|
A self-hosted backend can declare vendors too. `/init` returns them and c15t
|
|
76
88
|
merges them with the vendors in code. When both declare the same `id`, the
|
|
@@ -165,8 +177,8 @@ the same as withdrawing a category, so code the vendor already ran stops.
|
|
|
165
177
|
Test the production build in a private window with DevTools Network open and
|
|
166
178
|
filtered to `ads-twitter.com`:
|
|
167
179
|
|
|
168
|
-
1.
|
|
169
|
-
|
|
180
|
+
1. Register `xPixel()`. Open Privacy settings. The marketing row lists X
|
|
181
|
+
Pixel with a switch.
|
|
170
182
|
2. Allow marketing and save. `uwt.js` loads.
|
|
171
183
|
3. Open Privacy settings, switch X Pixel off and save. c15t reloads the
|
|
172
184
|
page. After the reload there is no `uwt.js` request, and other marketing
|
|
@@ -32,8 +32,6 @@ import type { ReactNode } from 'react';
|
|
|
32
32
|
|
|
33
33
|
import { scripts } from './scripts';
|
|
34
34
|
|
|
35
|
-
import 'c15t/react/styles.css';
|
|
36
|
-
|
|
37
35
|
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
38
36
|
|
|
39
37
|
// `control` is the stock banner. `wall` blocks the page until the visitor
|
|
@@ -258,7 +256,7 @@ import { resolveConsent } from 'c15t/next/server';
|
|
|
258
256
|
import { Suspense } from 'react';
|
|
259
257
|
import type { ReactNode } from 'react';
|
|
260
258
|
|
|
261
|
-
import {
|
|
259
|
+
import { consentOptions } from '@/c15t.server';
|
|
262
260
|
import { ExperimentConsent } from '@/components/experiment-consent';
|
|
263
261
|
import { bannerExperiment } from '@/lib/experiment';
|
|
264
262
|
import { bannerExperimentFlag } from '@/lib/flags';
|
|
@@ -268,7 +266,7 @@ import '@/styles/globals.css';
|
|
|
268
266
|
const ResolvedConsent = async ({ children }: { children: ReactNode }) => {
|
|
269
267
|
const arm = await bannerExperimentFlag();
|
|
270
268
|
const state = await resolveConsent({
|
|
271
|
-
|
|
269
|
+
...consentOptions,
|
|
272
270
|
// `off` keeps this visitor out of the experiment.
|
|
273
271
|
experiment: arm === 'off' ? undefined : { ...bannerExperiment, arm },
|
|
274
272
|
});
|