@c15t/react 3.0.0-alpha.2 → 3.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +91 -67
- package/README.md +21 -17
- package/SKILL.md +34 -0
- package/dist/aggregate-components.js +1 -1
- package/dist/chunk-warming.js +1 -1
- package/dist/component-hooks/use-headless-consent-ui.js +1 -1
- package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/component-hooks/use-panel-trigger.js +1 -1
- package/dist/components/consent-gate/atoms.js +1 -1
- package/dist/components/consent-gate/consent-gate.js +1 -1
- package/dist/components/consent-preferences-link/consent-preferences-link.js +1 -1
- package/dist/components/iab-panel/atoms/card.js +1 -1
- package/dist/components/iab-panel/atoms/feature-item.js +2 -0
- package/dist/components/iab-panel/atoms/header.js +1 -1
- package/dist/components/iab-panel/atoms/purpose-item.js +1 -1
- package/dist/components/iab-panel/atoms/stack-item.js +1 -1
- package/dist/components/iab-panel/iab-panel.js +1 -1
- package/dist/components/iab-panel/index.js +1 -1
- package/dist/components/iab-prompt/atoms/overlay.js +1 -1
- package/dist/components/iab-prompt/atoms/root.js +1 -1
- package/dist/components/panel/atoms/card.js +1 -1
- package/dist/components/panel/atoms/overlay.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel/index.js +1 -1
- package/dist/components/panel/panel.js +1 -1
- package/dist/components/panel-trigger/atoms/button.js +1 -1
- package/dist/components/panel-trigger/atoms/icon.js +1 -1
- package/dist/components/panel-trigger/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -0
- package/dist/components/panel-trigger/panel-trigger-toolbar.js +1 -1
- package/dist/components/preferences/atoms/accordion.js +1 -1
- package/dist/components/preferences/atoms/root.js +1 -1
- package/dist/components/preferences/atoms/vendor-list.js +1 -1
- package/dist/components/preferences/index.js +1 -1
- package/dist/components/prompt/atoms/overlay.js +1 -1
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/prompt/components.js +1 -1
- package/dist/components/prompt/policy-actions.js +1 -1
- package/dist/components/shared/libs/compose-refs.js +1 -0
- package/dist/components/shared/libs/slot.js +1 -1
- package/dist/components/shared/primitives/button.js +1 -1
- package/dist/components/shared/ui/accordion/accordion.js +1 -1
- package/dist/components/shared/ui/collapsible/collapsible.js +1 -1
- package/dist/components/shared/ui/dialog/dialog.js +1 -1
- package/dist/components/shared/ui/icon.js +1 -1
- package/dist/components/shared/ui/logo.js +1 -1
- package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
- package/dist/components/shared/ui/switch/switch.js +1 -1
- package/dist/components/shared/ui/tabs/tabs.js +1 -1
- package/dist/consent-theme.js +1 -0
- package/dist/context/consent-tracking-context.js +1 -1
- package/dist/devtools.js +1 -1
- package/dist/draft.js +1 -1
- package/dist/external-iab-context.js +1 -1
- package/dist/headless.js +1 -1
- package/dist/hooks/use-committed-ref.js +2 -0
- package/dist/hooks/use-focus-trap.js +1 -1
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +5 -45
- package/dist/iab-context.js +1 -1
- package/dist/index.js +1 -1
- package/dist/kernel-selector.js +1 -0
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/module-hooks/network-blocker.js +1 -1
- package/dist/module-hooks/network-hold.js +2 -0
- package/dist/module-hooks/persistence.js +1 -1
- package/dist/panel.js +1 -1
- package/dist/postcss-tailwind3.js +1 -0
- package/dist/preferences.js +1 -2
- package/dist/provider.js +1 -1
- package/dist/server/fetch-ssr-data.js +1 -1
- package/dist/server/headers.js +1 -1
- package/dist/server/normalize-url.js +1 -1
- package/dist/styles.tw3.css +16 -61
- package/dist/transports/offline.js +1 -1
- package/dist/ui-save.js +1 -1
- package/dist/utils/deferred-module.js +1 -1
- package/dist/utils/merge-slot-props.js +1 -1
- package/dist/utils/theme-utils.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/chunk-warming.d.ts +41 -8
- package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
- package/dist-types/component-hooks/use-headless-iab-consent-ui.d.ts +1 -1
- package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
- package/dist-types/components/iab-panel/atoms/purpose-item.d.ts +4 -1
- package/dist-types/components/iab-panel/atoms/stack-item.d.ts +6 -1
- package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
- package/dist-types/components/iab-panel/index.d.ts +6 -2
- package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
- package/dist-types/components/iab-prompt/iab-prompt.d.ts +2 -2
- package/dist-types/components/iab-prompt/index.d.ts +2 -2
- package/dist-types/components/panel/atoms/card.d.ts +1 -0
- package/dist-types/components/panel/index.d.ts +1 -0
- package/dist-types/components/panel/panel.d.ts +1 -1
- package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +20 -0
- package/dist-types/components/preferences/index.d.ts +1 -0
- package/dist-types/components/prompt/components.d.ts +6 -0
- package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
- package/dist-types/components/shared/ui/accordion/accordion.d.ts +1 -0
- package/dist-types/components/shared/ui/collapsible/collapsible.d.ts +1 -0
- package/dist-types/components/shared/ui/icon.d.ts +2 -1
- package/dist-types/components/shared/ui/logo.d.ts +0 -10
- package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +14 -0
- package/dist-types/components/shared/ui/switch/switch.d.ts +1 -0
- package/dist-types/components/shared/ui/tabs/tabs.d.ts +1 -0
- package/dist-types/consent-theme.d.ts +38 -0
- package/dist-types/context/consent-tracking-context.d.ts +10 -0
- package/dist-types/context/theme-context.d.ts +2 -2
- package/dist-types/context.d.ts +6 -0
- package/dist-types/devtools.d.ts +13 -2
- package/dist-types/draft.d.ts +50 -7
- package/dist-types/headless.d.ts +0 -1
- package/dist-types/hooks/use-committed-ref.d.ts +24 -0
- package/dist-types/hooks/use-focus-trap.d.ts +3 -1
- package/dist-types/hooks.d.ts +56 -13
- package/dist-types/iab.d.ts +1 -1
- package/dist-types/index.d.ts +4 -3
- package/dist-types/kernel-selector.d.ts +66 -0
- package/dist-types/module-hooks/iframe-blocker.d.ts +27 -0
- package/dist-types/module-hooks/network-blocker.d.ts +23 -0
- package/dist-types/module-hooks/network-hold.d.ts +47 -0
- package/dist-types/panel.d.ts +13 -1
- package/dist-types/postcss-tailwind3.d.ts +21 -0
- package/dist-types/preferences.d.ts +12 -1
- package/dist-types/provider.d.ts +62 -12
- package/dist-types/server/headers.d.ts +14 -4
- package/dist-types/server/normalize-url.d.ts +14 -7
- package/dist-types/server/types.d.ts +12 -1
- package/dist-types/transports/offline.d.ts +7 -16
- package/dist-types/ui-config-context.d.ts +9 -1
- package/dist-types/ui-save.d.ts +21 -1
- package/dist-types/utils/deferred-module.d.ts +3 -1
- package/dist-types/utils/merge-slot-props.d.ts +15 -1
- package/dist-types/utils/theme-utils.d.ts +17 -2
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +91 -67
- package/docs/assets/v3/bottom-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/brand-preferences.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/headless-bar-html.png +0 -0
- package/docs/assets/v3/headless-bar-mobile.png +0 -0
- package/docs/assets/v3/headless-bar.png +0 -0
- package/docs/assets/v3/slim-bar.png +0 -0
- package/docs/concepts/choose-your-setup.md +87 -0
- package/docs/{frameworks/react/concepts → concepts}/consent-categories.md +30 -8
- package/docs/concepts/consent-state.md +357 -0
- package/docs/{guides → concepts}/data-fetching.md +31 -27
- package/docs/concepts/how-consent-works.md +123 -0
- package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +7 -59
- package/docs/customization/class-names.md +202 -0
- package/docs/customization/dark-mode.md +157 -0
- package/docs/customization/motion.md +119 -0
- package/docs/customization/overview.md +67 -33
- package/docs/customization/recipes.md +839 -47
- package/docs/customization/slots.md +216 -35
- package/docs/customization/stylesheets.md +147 -0
- package/docs/customization/tailwind.md +842 -0
- package/docs/customization/tokens.md +166 -36
- package/docs/customization/translations.md +61 -3
- package/docs/frameworks/react/callbacks.md +118 -0
- package/docs/frameworks/react/clear-on-revocation.md +106 -0
- package/docs/frameworks/react/components/consent-banner.md +42 -28
- package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
- package/docs/frameworks/react/components/consent-dialog-trigger.md +81 -76
- package/docs/frameworks/react/components/consent-dialog.md +100 -55
- package/docs/frameworks/react/components/consent-gate.md +45 -24
- package/docs/frameworks/react/components/consent-provider.md +126 -0
- package/docs/frameworks/react/components/consent-widget.md +32 -20
- package/docs/frameworks/react/components/dev-tools.md +52 -84
- package/docs/frameworks/react/components.md +105 -0
- package/docs/frameworks/react/compose.md +354 -0
- package/docs/frameworks/react/content-security-policy.md +126 -0
- package/docs/frameworks/react/customize.md +250 -0
- package/docs/frameworks/react/embeds.md +89 -0
- package/docs/frameworks/react/hooks.md +65 -0
- package/docs/frameworks/react/iab.md +222 -0
- package/docs/frameworks/react/network-blocker.md +140 -0
- package/docs/frameworks/react/quickstart.md +139 -101
- package/docs/frameworks/react/rendering.md +128 -0
- package/docs/frameworks/react/scripts.md +115 -0
- package/docs/frameworks/react/translations.md +155 -0
- package/docs/frameworks/react/troubleshooting.md +92 -47
- package/docs/frameworks/react/vendor-consent.md +170 -0
- package/docs/guides/banner-experiments.md +654 -0
- package/docs/guides/troubleshooting.md +120 -47
- package/docs/guides/verify-consent.md +81 -49
- package/docs/integrations/adobe-analytics.md +168 -160
- package/docs/integrations/ahrefs-analytics.md +153 -155
- package/docs/integrations/amplitude.md +163 -157
- package/docs/integrations/building-integrations.md +136 -37
- package/docs/integrations/clearbit.md +155 -155
- package/docs/integrations/cloudflare-web-analytics.md +157 -157
- package/docs/integrations/cloudflare-zaraz.md +210 -262
- package/docs/integrations/crisp.md +165 -159
- package/docs/integrations/databuddy.md +158 -174
- package/docs/integrations/fathom-analytics.md +159 -157
- package/docs/integrations/front-chat.md +322 -0
- package/docs/integrations/google-maps.md +119 -84
- package/docs/integrations/google-tag-manager.md +179 -164
- package/docs/integrations/google-tag.md +164 -161
- package/docs/integrations/heap.md +164 -156
- package/docs/integrations/hightouch.md +162 -158
- package/docs/integrations/hotjar.md +160 -156
- package/docs/integrations/intercom.md +184 -154
- package/docs/integrations/klaviyo.md +486 -0
- package/docs/integrations/linkedin-insights.md +175 -151
- package/docs/integrations/logrocket.md +161 -157
- package/docs/integrations/matomo-analytics.md +189 -179
- package/docs/integrations/meta-pixel.md +189 -151
- package/docs/integrations/microsoft-clarity.md +164 -156
- package/docs/integrations/microsoft-uet.md +149 -155
- package/docs/integrations/mixpanel-analytics.md +156 -161
- package/docs/integrations/one-dollar-stats.md +306 -0
- package/docs/integrations/openai-pixel.md +205 -302
- package/docs/integrations/overview.md +143 -83
- package/docs/integrations/pinterest-tag.md +329 -0
- package/docs/integrations/pirsch.md +170 -160
- package/docs/integrations/plausible-analytics.md +173 -159
- package/docs/integrations/posthog.md +227 -242
- package/docs/integrations/promptwatch.md +155 -155
- package/docs/integrations/reddit-pixel.md +186 -158
- package/docs/integrations/rudderstack.md +202 -187
- package/docs/integrations/rybbit-analytics.md +172 -161
- package/docs/integrations/segment.md +183 -155
- package/docs/integrations/snapchat-pixel.md +187 -157
- package/docs/integrations/tiktok-pixel.md +172 -151
- package/docs/integrations/umami-analytics.md +164 -159
- package/docs/integrations/vercel-analytics.md +167 -158
- package/docs/integrations/x-pixel.md +177 -151
- package/docs/integrations/youtube.md +122 -87
- package/docs/upgrade-v3.md +490 -354
- package/package.json +15 -9
- package/dist/component-hooks/use-manager.js +0 -2
- package/dist-types/component-hooks/use-manager.d.ts +0 -45
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/frameworks/react/components/consent-manager-provider.md +0 -75
- package/docs/frameworks/react/hooks/use-consent-manager/overview.md +0 -58
- package/docs/frameworks/react/iab/overview.md +0 -53
- package/docs/frameworks/react/script-loader.md +0 -63
- package/docs/frameworks/react/styling/overview.md +0 -217
- package/docs/guides/consent-state.md +0 -60
- package/docs/guides/deployment-modes.md +0 -75
- package/docs/integrations/clear-on-revocation.md +0 -167
- package/docs/integrations/granular-consent.md +0 -208
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Network blocker
|
|
3
|
+
description: Hold fetch and XMLHttpRequest calls in a React app until their
|
|
4
|
+
consent category is allowed, with networkBlocker rules in the ConsentProvider
|
|
5
|
+
options.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Add rules
|
|
10
|
+
|
|
11
|
+
Some SDKs are already on the page and send beacons or API calls of their own.
|
|
12
|
+
Add `networkBlocker` rules to the provider options to stop `fetch` and
|
|
13
|
+
`XMLHttpRequest` calls to those domains until the category is allowed. Load
|
|
14
|
+
vendor SDKs through [`scripts`](./scripts.md) first, so they
|
|
15
|
+
do not run at all before consent.
|
|
16
|
+
|
|
17
|
+
This partial example adds the option next to the existing `mode` and
|
|
18
|
+
`scripts`:
|
|
19
|
+
|
|
20
|
+
```tsx title="src/consent.tsx"
|
|
21
|
+
const networkBlocker = {
|
|
22
|
+
rules: [
|
|
23
|
+
{
|
|
24
|
+
id: 'google-analytics',
|
|
25
|
+
domain: 'google-analytics.com',
|
|
26
|
+
category: 'measurement',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'meta-pixel',
|
|
30
|
+
domain: 'facebook.com',
|
|
31
|
+
pathIncludes: '/tr',
|
|
32
|
+
category: 'marketing',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
<ConsentProvider options={{ mode, scripts, networkBlocker }}>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Match requests with rules
|
|
41
|
+
|
|
42
|
+
Each rule names a `domain` and the consent `category` a request needs. The
|
|
43
|
+
domain also matches its subdomains: `google-analytics.com` covers
|
|
44
|
+
`www.google-analytics.com`. `pathIncludes` narrows the rule to paths that
|
|
45
|
+
contain a substring, and `methods` narrows it to HTTP methods. A request is
|
|
46
|
+
blocked when a matching rule's condition is not met by the visitor's
|
|
47
|
+
effective permissions.
|
|
48
|
+
|
|
49
|
+
`category` takes the same conditions as scripts:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
{ category: 'measurement' }
|
|
53
|
+
{ category: { and: ['measurement', 'marketing'] } }
|
|
54
|
+
{ category: { or: ['measurement', 'marketing'] } }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Add `vendor` to also block the request while the visitor has turned that
|
|
58
|
+
vendor off. IAB TCF rules use `vendorId` and the `iab*` purpose fields
|
|
59
|
+
instead.
|
|
60
|
+
|
|
61
|
+
| Option | Default | Purpose |
|
|
62
|
+
| -------------------- | -------- | ------------------------------------------------------------ |
|
|
63
|
+
| `rules` | required | Rules described above |
|
|
64
|
+
| `enabled` | `true` | Set `false` to keep the rules but stop blocking |
|
|
65
|
+
| `logBlockedRequests` | `true` | Log each blocked request with `console.warn` |
|
|
66
|
+
| `onRequestBlocked` | none | Called with `{ method, url, rule }` for each blocked request |
|
|
67
|
+
|
|
68
|
+
## What a blocked request looks like
|
|
69
|
+
|
|
70
|
+
The blocker wraps `window.fetch` and `XMLHttpRequest`. A blocked `fetch`
|
|
71
|
+
resolves to a `451` response with the status text
|
|
72
|
+
`Request blocked by consent`, and nothing is sent. A blocked XHR is aborted
|
|
73
|
+
and fires an `error` event. Requests that match no rule are not delayed.
|
|
74
|
+
|
|
75
|
+
## When blocking starts
|
|
76
|
+
|
|
77
|
+
The provider holds matching requests from its first render in the browser,
|
|
78
|
+
before any of its children render or run effects. That covers requests from
|
|
79
|
+
child components, including their mount effects, and from effects in
|
|
80
|
+
components rendered next to the provider. The blocker module itself loads
|
|
81
|
+
after mount and decides each held request. Apps without `networkBlocker` do
|
|
82
|
+
not download it.
|
|
83
|
+
|
|
84
|
+
The standalone `useNetworkBlocker` hook works the same way from the first
|
|
85
|
+
render of the component that calls it. That render patches `fetch` and
|
|
86
|
+
`XMLHttpRequest`. If React throws the render away and never commits it, the
|
|
87
|
+
hold ends after 10 seconds. Nothing checked consent for the requests it held,
|
|
88
|
+
so they fail the way the blocker fails a blocked request: a 451 response for
|
|
89
|
+
`fetch`, a failed XHR. The same happens when the component unmounts before
|
|
90
|
+
the blocker loads.
|
|
91
|
+
|
|
92
|
+
While consent is unknown, a matching request that would be blocked waits
|
|
93
|
+
instead of failing. Consent is unknown until the policy has loaded, which is
|
|
94
|
+
also when a returning visitor's stored choice takes effect. The request is
|
|
95
|
+
then sent if the choice allows it and blocked otherwise. If the policy fails
|
|
96
|
+
to load, optional categories stay denied and waiting requests are blocked.
|
|
97
|
+
If the policy request never finishes, they keep waiting and are never sent.
|
|
98
|
+
|
|
99
|
+
A synchronous XHR cannot wait. Before the blocker module has loaded, a
|
|
100
|
+
matching one throws a `NetworkError` from `send()`. After that, one that
|
|
101
|
+
consent does not allow yet is blocked.
|
|
102
|
+
|
|
103
|
+
## What the network blocker cannot stop
|
|
104
|
+
|
|
105
|
+
The blocker only sees requests made after the provider starts rendering in
|
|
106
|
+
the browser, through `fetch` or `XMLHttpRequest`. It cannot stop:
|
|
107
|
+
|
|
108
|
+
* Code that runs before the provider renders: inline scripts in the HTML,
|
|
109
|
+
third-party tags in `<head>`, scripts loaded before hydration (such as
|
|
110
|
+
`next/script` with `beforeInteractive`), and client modules that evaluate
|
|
111
|
+
earlier. Webpack builds evaluate a route's client component modules when
|
|
112
|
+
its chunk loads, so their top-level code runs first. Turbopack evaluates a
|
|
113
|
+
client component module when its first element renders, which inside the
|
|
114
|
+
provider is after blocking starts.
|
|
115
|
+
* Code that saved its own reference to `fetch` or `XMLHttpRequest` before the
|
|
116
|
+
provider rendered.
|
|
117
|
+
* `navigator.sendBeacon`, `WebSocket`, `EventSource`, and requests made by
|
|
118
|
+
`<img>`, `<script>` or `<iframe>` elements, web workers and service
|
|
119
|
+
workers.
|
|
120
|
+
* With the standalone `useNetworkBlocker` hook instead of the provider
|
|
121
|
+
option, requests sent before the component that calls it renders.
|
|
122
|
+
Blocking starts in that component's first render, not the provider's.
|
|
123
|
+
|
|
124
|
+
Keep tracking calls out of that window:
|
|
125
|
+
|
|
126
|
+
* Send them from an effect or an event handler, never at module top level.
|
|
127
|
+
* Load vendor SDKs through `scripts` instead of a `<script>` tag or
|
|
128
|
+
`next/script`, so they wait for consent before they run at all.
|
|
129
|
+
* Check `useConsent('measurement')` (or the category you need) before you
|
|
130
|
+
call a vendor from your own code, and treat the blocker as a backstop.
|
|
131
|
+
|
|
132
|
+
## Verify the blocked requests
|
|
133
|
+
|
|
134
|
+
Open DevTools Network in a private window, under a policy that asks for
|
|
135
|
+
consent.
|
|
136
|
+
|
|
137
|
+
1. Before a choice, requests matching a rule are absent, and the console logs
|
|
138
|
+
each blocked request.
|
|
139
|
+
2. Allow the rule's category and save. Matching requests go out.
|
|
140
|
+
3. Reject, reload, and check that they stay absent.
|
|
@@ -1,143 +1,181 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Quickstart
|
|
3
|
-
description:
|
|
4
|
-
|
|
3
|
+
description: Add a consent banner, preferences dialog and consent-gated scripts
|
|
4
|
+
to a React single-page app built with Vite, using Inth as the consent backend.
|
|
5
5
|
group: frameworks
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Before you start
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
framework's adapter for request prefetch.
|
|
10
|
+
This guide sets up a client-rendered React app built with Vite. The browser
|
|
11
|
+
loads the page, asks the consent backend for the visitor's policy, then shows
|
|
12
|
+
the banner or runs the scripts the visitor allowed.
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
* **Next.js or TanStack Start?** Use that framework's guide from the
|
|
15
|
+
[framework list](https://c15t.com/docs/frameworks). Their adapters resolve consent on the
|
|
16
|
+
server.
|
|
17
|
+
* **React Router framework mode, Remix or another server-rendered React app?**
|
|
18
|
+
This guide still works. The server renders the page without consent state and
|
|
19
|
+
the browser resolves it after hydration. Read
|
|
20
|
+
[rendering](./rendering.md#react-router-remix-and-other-server-rendered-apps)
|
|
21
|
+
for what that means for the first paint.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
You need an [Inth](https://inth.com) project. In the project, set the policy
|
|
24
|
+
rules for your regions, add your site's origin to the trusted origins, and copy
|
|
25
|
+
the backend URL.
|
|
26
|
+
[Choose your setup](../../concepts/choose-your-setup.md#decide-who-runs-the-consent-backend)
|
|
27
|
+
covers self-hosting and offline mode.
|
|
25
28
|
|
|
26
|
-
##
|
|
29
|
+
## Install c15t
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
| Package manager | Command |
|
|
32
|
+
| :-------------- | :------------------------------------------------ |
|
|
33
|
+
| npm | `npm install c15t@alpha @c15t/integrations@alpha` |
|
|
34
|
+
| pnpm | `pnpm add c15t@alpha @c15t/integrations@alpha` |
|
|
35
|
+
| yarn | `yarn add c15t@alpha @c15t/integrations@alpha` |
|
|
36
|
+
| bun | `bun add c15t@alpha @c15t/integrations@alpha` |
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
`c15t` contains the React adapter at `c15t/react`. `@c15t/integrations` holds the
|
|
39
|
+
vendor loaders used below.
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
VITE_C15T_BACKEND_URL=
|
|
36
|
-
VITE_POSTHOG_KEY=
|
|
37
|
-
VITE_POSTHOG_HOST=
|
|
38
|
-
VITE_X_PIXEL_ID=
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Copy the exact backend endpoint from Inth. The vendor IDs are optional: leaving
|
|
42
|
-
an ID empty disables that integration. When enabling PostHog, fill in both its project key and the API host
|
|
43
|
-
shown in your PostHog project. Restart Vite after editing these values and
|
|
44
|
-
rebuild for deployment. Other bundlers need their own configuration mechanism.
|
|
41
|
+
## Register your vendor scripts
|
|
45
42
|
|
|
46
|
-
Create `src/scripts.ts`. PostHog waits for measurement permission
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
Create `src/scripts.ts`. PostHog waits for measurement permission and X Pixel
|
|
44
|
+
waits for marketing permission, so your policy must include both categories.
|
|
45
|
+
Remove any other PostHog or X Pixel loader from your app so each vendor loads
|
|
46
|
+
once.
|
|
49
47
|
|
|
50
48
|
```ts title="src/scripts.ts"
|
|
51
|
-
import { posthog } from '@c15t/
|
|
52
|
-
import { xPixel } from '@c15t/
|
|
53
|
-
|
|
54
|
-
const posthogKey = import.meta.env.VITE_POSTHOG_KEY;
|
|
55
|
-
const xPixelId = import.meta.env.VITE_X_PIXEL_ID;
|
|
49
|
+
import { posthog } from '@c15t/integrations/posthog';
|
|
50
|
+
import { xPixel } from '@c15t/integrations/x-pixel';
|
|
56
51
|
|
|
57
52
|
export const scripts = [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
loadMode: 'after-consent',
|
|
65
|
-
}),
|
|
66
|
-
]
|
|
67
|
-
: []),
|
|
68
|
-
...(xPixelId ? [xPixel({ pixelId: xPixelId })] : []),
|
|
53
|
+
posthog({
|
|
54
|
+
id: 'phc_your_project_key',
|
|
55
|
+
initOptions: { cookieless_mode: 'never' },
|
|
56
|
+
loadMode: 'after-consent',
|
|
57
|
+
}),
|
|
58
|
+
xPixel({ pixelId: 'your-pixel-id' }),
|
|
69
59
|
];
|
|
70
60
|
```
|
|
71
61
|
|
|
72
|
-
|
|
62
|
+
Replace `phc_your_project_key` with your PostHog project key and
|
|
63
|
+
`your-pixel-id` with your X Pixel ID. PostHog uses its EU host; add
|
|
64
|
+
`region: 'us'` for a US project. Remove the loader for a vendor you don't use.
|
|
65
|
+
|
|
66
|
+
`loadMode: 'after-consent'` keeps the PostHog SDK off the page until the
|
|
67
|
+
visitor allows measurement. `cookieless_mode: 'never'` turns off PostHog's
|
|
68
|
+
cookieless capture. See [integrations](../../integrations/overview.md) for other
|
|
69
|
+
vendors.
|
|
70
|
+
|
|
71
|
+
## Mount the provider
|
|
72
|
+
|
|
73
|
+
Create `src/consent.tsx`. `ConsentProvider` creates the consent runtime and
|
|
74
|
+
loads the scripts. `ConsentBanner` and `ConsentDialog` render when the policy
|
|
75
|
+
asks for them, and `ConsentDialogLink` keeps preferences reachable after the
|
|
76
|
+
banner closes.
|
|
73
77
|
|
|
74
78
|
```tsx title="src/consent.tsx"
|
|
75
|
-
import type { ReactNode } from 'react';
|
|
76
79
|
import {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
ConsentBanner,
|
|
81
|
+
ConsentDialog,
|
|
82
|
+
ConsentDialogLink,
|
|
83
|
+
ConsentProvider,
|
|
84
|
+
hosted,
|
|
82
85
|
} from 'c15t/react';
|
|
83
|
-
import '
|
|
86
|
+
import type { ReactNode } from 'react';
|
|
87
|
+
|
|
84
88
|
import { scripts } from './scripts';
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const mode = hosted({ url:
|
|
89
|
-
|
|
90
|
-
export
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
import 'c15t/react/styles.css';
|
|
91
|
+
|
|
92
|
+
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
93
|
+
|
|
94
|
+
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
95
|
+
<ConsentProvider options={{ mode, scripts }}>
|
|
96
|
+
{children}
|
|
97
|
+
<ConsentBanner />
|
|
98
|
+
<ConsentDialog />
|
|
99
|
+
<footer>
|
|
100
|
+
<ConsentDialogLink>Privacy settings</ConsentDialogLink>
|
|
101
|
+
</footer>
|
|
102
|
+
</ConsentProvider>
|
|
103
|
+
);
|
|
100
104
|
```
|
|
101
105
|
|
|
106
|
+
Replace `https://your-project.inth.app` with your project's backend URL,
|
|
107
|
+
exactly as Inth shows it, including any path prefix.
|
|
108
|
+
|
|
109
|
+
Wrap your app with it in `src/main.tsx`:
|
|
110
|
+
|
|
102
111
|
```tsx title="src/main.tsx"
|
|
103
112
|
import { createRoot } from 'react-dom/client';
|
|
104
|
-
|
|
113
|
+
|
|
114
|
+
import { App } from './app';
|
|
105
115
|
import { Consent } from './consent';
|
|
106
116
|
|
|
107
117
|
const root = document.getElementById('root');
|
|
108
|
-
if (!root)
|
|
109
|
-
|
|
118
|
+
if (!root) {
|
|
119
|
+
throw new Error('Missing #root element');
|
|
120
|
+
}
|
|
121
|
+
createRoot(root).render(
|
|
122
|
+
<Consent>
|
|
123
|
+
<App />
|
|
124
|
+
</Consent>
|
|
125
|
+
);
|
|
110
126
|
```
|
|
111
127
|
|
|
112
|
-
Keep `Consent` mounted
|
|
113
|
-
|
|
114
|
-
The
|
|
128
|
+
Keep `Consent` mounted for the life of the app. If you use a client-side
|
|
129
|
+
router, render the router inside `Consent` so navigation does not remount the
|
|
130
|
+
provider. The stylesheet import in `src/consent.tsx` styles the banner and
|
|
131
|
+
dialog; import it once.
|
|
132
|
+
|
|
133
|
+
## Gate your own features
|
|
115
134
|
|
|
116
|
-
|
|
135
|
+
Read a category's permission with `useConsent` inside the provider:
|
|
117
136
|
|
|
118
|
-
```tsx
|
|
137
|
+
```tsx title="src/marketing-banner.tsx"
|
|
119
138
|
import { useConsent } from 'c15t/react';
|
|
120
139
|
|
|
121
|
-
export function
|
|
122
|
-
|
|
140
|
+
export function MarketingBanner() {
|
|
141
|
+
return useConsent('marketing') ? <aside>Spring sale</aside> : null;
|
|
123
142
|
}
|
|
124
143
|
```
|
|
125
144
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
145
|
+
`useConsent` returns the permission right now. Under an opt-out policy it can be
|
|
146
|
+
`true` before the visitor has chosen anything. Read
|
|
147
|
+
[how consent works](../../concepts/how-consent-works.md#a-permission-is-not-a-recorded-choice)
|
|
148
|
+
before you record or report choices. For iframes, use
|
|
149
|
+
[`ConsentGate`](./components/consent-gate.md).
|
|
150
|
+
|
|
151
|
+
## Check that it works
|
|
152
|
+
|
|
153
|
+
Build and preview the production bundle with `vite build` and `vite preview`,
|
|
154
|
+
then open the site in a private window with DevTools open on the Network tab.
|
|
155
|
+
Test under a policy that asks for a choice, such as an EU opt-in policy.
|
|
156
|
+
|
|
157
|
+
1. **Before a choice.** The banner shows. There are no requests to
|
|
158
|
+
`posthog.com` or `static.ads-twitter.com`.
|
|
159
|
+
2. **Reject All.** The banner closes and the vendor requests stay absent.
|
|
160
|
+
3. **Reload.** The banner stays closed and the vendor requests stay absent.
|
|
161
|
+
4. **Privacy settings.** The footer link opens the dialog with **Analytics** (the
|
|
162
|
+
`measurement` category) and **Marketing** off. Turn on **Analytics** and save. PostHog's `array.js` loads and
|
|
163
|
+
X Pixel stays absent.
|
|
164
|
+
5. **Turn Analytics off again.** The page reloads and PostHog does not load.
|
|
165
|
+
|
|
166
|
+
If the banner never appears, check the Network tab for the request to
|
|
167
|
+
`/init` on your backend URL. [Troubleshooting](./troubleshooting.md)
|
|
168
|
+
covers the common causes. [Verify consent](../../guides/verify-consent.md) has the
|
|
169
|
+
full release checklist.
|
|
170
|
+
|
|
171
|
+
## Next steps
|
|
172
|
+
|
|
173
|
+
* [Rendering](./rendering.md) for server-rendered React apps and
|
|
174
|
+
what the first paint contains.
|
|
175
|
+
* [Scripts and embeds](./scripts.md) for more vendors, network
|
|
176
|
+
blocking and data cleanup.
|
|
177
|
+
* [Customize](./customize.md) for colors, fonts, layout and copy.
|
|
178
|
+
* [Components](./components/consent-provider.md) for
|
|
179
|
+
every provider option and component prop.
|
|
180
|
+
* The runnable app in `examples/react` of the
|
|
181
|
+
[c15t repository](https://github.com/c15t/c15t) contains this setup.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Rendering and deployment
|
|
3
|
+
description: How c15t resolves consent in client-rendered React apps, React
|
|
4
|
+
Router framework mode, Remix and other server-rendered React apps without a
|
|
5
|
+
c15t adapter.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Pick your rendering path
|
|
10
|
+
|
|
11
|
+
`c15t/react` resolves consent in the browser. That works for every React app,
|
|
12
|
+
including server-rendered ones. What changes is what the first HTML contains.
|
|
13
|
+
|
|
14
|
+
| Your app | Where consent resolves | Setup |
|
|
15
|
+
| ------------------------------------------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| Vite, or another client-rendered single-page app | Browser, after the bundle loads | [Quickstart](./quickstart.md) |
|
|
17
|
+
| Prerendered or static HTML | Browser, after the page loads | [Quickstart](./quickstart.md), see [static pages](#static-and-prerendered-pages) |
|
|
18
|
+
| React Router framework mode, Remix, or another server-rendered React app | Browser, after hydration | [Quickstart](./quickstart.md), see [server-rendered apps](#react-router-remix-and-other-server-rendered-apps) |
|
|
19
|
+
| Next.js or TanStack Start | Server, per request | That framework's guide in the [framework list](https://c15t.com/docs/frameworks) |
|
|
20
|
+
|
|
21
|
+
Pick the Next.js or TanStack Start adapter if you use those frameworks. They
|
|
22
|
+
read the visitor's cookie and location on the server, so the banner can be in
|
|
23
|
+
the first HTML. [Choose your setup](../../concepts/choose-your-setup.md#server-rendering-or-browser-rendering)
|
|
24
|
+
explains the difference in general terms.
|
|
25
|
+
|
|
26
|
+
## Client-rendered apps
|
|
27
|
+
|
|
28
|
+
The [quickstart](./quickstart.md) setup runs entirely in the
|
|
29
|
+
browser:
|
|
30
|
+
|
|
31
|
+
1. The page loads with no consent state. Every optional category is denied.
|
|
32
|
+
2. `ConsentProvider` requests `GET /init` from your backend URL. The backend
|
|
33
|
+
picks the visitor's policy from their location.
|
|
34
|
+
3. The provider applies the policy and any stored choice. It shows the banner
|
|
35
|
+
if the policy asks for one, and loads the scripts the visitor allowed.
|
|
36
|
+
|
|
37
|
+
Until step 3 finishes, gated scripts and `ConsentGate` embeds stay blocked. If
|
|
38
|
+
the backend request fails, they stay blocked and no banner shows.
|
|
39
|
+
|
|
40
|
+
## React Router, Remix and other server-rendered apps
|
|
41
|
+
|
|
42
|
+
React Router framework mode, Remix and similar frameworks have no c15t adapter.
|
|
43
|
+
Use the quickstart's `Consent` component and render it in your root component,
|
|
44
|
+
around the outlet. In React Router framework mode that is `app/root.tsx`.
|
|
45
|
+
|
|
46
|
+
`ConsentProvider` renders on the server without touching browser APIs, and the
|
|
47
|
+
browser takes over after hydration:
|
|
48
|
+
|
|
49
|
+
* The server HTML contains your page but no banner and no consent state.
|
|
50
|
+
* After hydration the browser requests `/init`, then shows the banner if the
|
|
51
|
+
policy asks for one.
|
|
52
|
+
* The server render and the first browser render agree, so hydration does not
|
|
53
|
+
warn or change a stored choice.
|
|
54
|
+
|
|
55
|
+
One detail differs from the Vite setup: import `c15t/react/styles.css` where
|
|
56
|
+
your framework loads global CSS, so the server HTML links it.
|
|
57
|
+
|
|
58
|
+
### Skip the browser's first init request
|
|
59
|
+
|
|
60
|
+
This is optional. `fetchSSRData` from `c15t/react/server` calls the backend's
|
|
61
|
+
`/init` from your server loader, forwarding the visitor's location and
|
|
62
|
+
language headers. Pass the result to the browser, wrap it in a promise and give
|
|
63
|
+
it to `hosted({ url, initialData })`. The provider's first init then uses that
|
|
64
|
+
response instead of a network request.
|
|
65
|
+
|
|
66
|
+
Know its limits before you add it:
|
|
67
|
+
|
|
68
|
+
* It saves the browser's round trip to the backend. It does not put the banner
|
|
69
|
+
in the server HTML; the banner still mounts after hydration.
|
|
70
|
+
* Every page view makes a server-to-server `/init` request. Do not cache the
|
|
71
|
+
loader response, because it contains one visitor's policy.
|
|
72
|
+
* It returns `undefined` when the request has no location or language headers,
|
|
73
|
+
and the browser then requests `/init` as usual.
|
|
74
|
+
* `mode` is read once, when the provider mounts. Create the `hosted()` mode once
|
|
75
|
+
in the browser rather than on every render.
|
|
76
|
+
* A relative `backendURL` resolves against the request's `host` header, over
|
|
77
|
+
`https` for a domain name and over `http` for `localhost`, an IP address or
|
|
78
|
+
a single-label host. `fetchSSRData` ignores `x-forwarded-host`,
|
|
79
|
+
`x-forwarded-proto` and `forwarded`, and does not send them to the backend.
|
|
80
|
+
Behind a proxy that overwrites those headers, pass
|
|
81
|
+
`trustForwardedHeaders: true` to use them.
|
|
82
|
+
* If your server answers requests for any `Host` header, such as a Node server
|
|
83
|
+
exposed directly or a proxy that forwards `Host` from its default virtual
|
|
84
|
+
host, pass `fetchSSRData` an absolute `backendURL`. A relative one resolves
|
|
85
|
+
against whatever `Host` the request sent.
|
|
86
|
+
|
|
87
|
+
### Render the banner in the server HTML
|
|
88
|
+
|
|
89
|
+
c15t has no framework-neutral helper for this. `ConsentProvider` accepts a
|
|
90
|
+
server-resolved `prefetch` state, but the Next.js and TanStack Start adapters
|
|
91
|
+
build that state from the request's cookie, headers and a cached policy
|
|
92
|
+
manifest. Building it by hand ties your app to internal shapes. If the banner
|
|
93
|
+
must be part of the first paint, use one of those adapters. Otherwise keep the
|
|
94
|
+
browser path.
|
|
95
|
+
|
|
96
|
+
## Static and prerendered pages
|
|
97
|
+
|
|
98
|
+
A prerendered page is the same HTML for every visitor, so it cannot contain one
|
|
99
|
+
visitor's consent. Use the quickstart setup unchanged. The browser talks to the
|
|
100
|
+
backend URL directly, so the static host needs no server route. Add the site's
|
|
101
|
+
origin to the trusted origins in your Inth project.
|
|
102
|
+
|
|
103
|
+
Do not cache a response that contains a visitor's consent in a CDN or a shared
|
|
104
|
+
page cache. [Data fetching](../../concepts/data-fetching.md) covers what is safe to
|
|
105
|
+
cache.
|
|
106
|
+
|
|
107
|
+
## Where the browser sends requests
|
|
108
|
+
|
|
109
|
+
With `hosted({ url })`, the browser calls two backend endpoints:
|
|
110
|
+
|
|
111
|
+
| Request | When |
|
|
112
|
+
| --------------------- | ------------------------------------------ |
|
|
113
|
+
| `GET {url}/init` | Once per page load, to resolve the policy |
|
|
114
|
+
| `POST {url}/subjects` | When the visitor accepts, rejects or saves |
|
|
115
|
+
|
|
116
|
+
Both go to the backend's origin, so it must list your site as a trusted origin.
|
|
117
|
+
[Data fetching](../../concepts/data-fetching.md) compares this with proxies and
|
|
118
|
+
custom transports.
|
|
119
|
+
|
|
120
|
+
## Check the rendering path
|
|
121
|
+
|
|
122
|
+
1. Open the page in a private window and view the page source. In a
|
|
123
|
+
server-rendered app the HTML contains your content and no element with
|
|
124
|
+
`data-testid="consent-banner-root"`.
|
|
125
|
+
2. In DevTools Network, filter by your backend URL. One `/init` request runs
|
|
126
|
+
after the JavaScript loads, or none if you pass `initialData`.
|
|
127
|
+
3. The banner appears once that request finishes. Vendor requests stay absent
|
|
128
|
+
until you allow their category.
|