@c15t/react 3.0.0-alpha.3 → 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 -72
- package/README.md +21 -17
- package/SKILL.md +34 -0
- package/dist/component-hooks/use-headless-consent-ui.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/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/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/root.js +1 -1
- package/dist/components/panel-trigger/atoms/button.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/preferences/atoms/accordion.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/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/dialog/dialog.js +1 -1
- package/dist/components/shared/ui/icon.js +1 -1
- package/dist/context/consent-tracking-context.js +1 -1
- package/dist/devtools.js +1 -1
- package/dist/draft.js +1 -1
- package/dist/hooks/use-focus-trap.js +1 -1
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +4 -4
- package/dist/index.js +1 -1
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/postcss-tailwind3.js +1 -0
- 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 -16
- package/dist/transports/offline.js +1 -1
- package/dist/ui-save.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/component-hooks/use-headless-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/iab-panel.d.ts +2 -2
- package/dist-types/components/iab-panel/index.d.ts +5 -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/prompt/components.d.ts +6 -0
- package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
- package/dist-types/components/shared/ui/icon.d.ts +2 -1
- 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 +10 -4
- package/dist-types/hooks/use-focus-trap.d.ts +3 -1
- package/dist-types/hooks.d.ts +54 -13
- package/dist-types/iab.d.ts +1 -1
- package/dist-types/index.d.ts +2 -2
- package/dist-types/postcss-tailwind3.d.ts +21 -0
- package/dist-types/provider.d.ts +22 -5
- 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 +7 -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 -72
- 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 +22 -7
- package/docs/{guides → concepts}/consent-state.md +71 -101
- 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 -34
- package/docs/customization/recipes.md +839 -49
- 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 +163 -96
- 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 +56 -50
- package/docs/frameworks/react/components/consent-gate.md +37 -20
- package/docs/frameworks/react/components/consent-provider.md +126 -0
- package/docs/frameworks/react/components/consent-widget.md +23 -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/overview.md → hooks.md} +21 -16
- package/docs/frameworks/react/{iab/overview.md → iab.md} +80 -31
- package/docs/frameworks/react/network-blocker.md +35 -41
- 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 +167 -159
- package/docs/integrations/ahrefs-analytics.md +152 -154
- package/docs/integrations/amplitude.md +162 -156
- package/docs/integrations/building-integrations.md +136 -37
- package/docs/integrations/clearbit.md +154 -154
- package/docs/integrations/cloudflare-web-analytics.md +156 -156
- package/docs/integrations/cloudflare-zaraz.md +209 -261
- package/docs/integrations/crisp.md +164 -158
- package/docs/integrations/databuddy.md +157 -173
- package/docs/integrations/fathom-analytics.md +158 -156
- package/docs/integrations/front-chat.md +167 -167
- package/docs/integrations/google-maps.md +118 -83
- package/docs/integrations/google-tag-manager.md +178 -163
- package/docs/integrations/google-tag.md +163 -160
- package/docs/integrations/heap.md +163 -155
- package/docs/integrations/hightouch.md +161 -157
- package/docs/integrations/hotjar.md +159 -155
- package/docs/integrations/intercom.md +183 -153
- package/docs/integrations/klaviyo.md +486 -0
- package/docs/integrations/linkedin-insights.md +174 -150
- package/docs/integrations/logrocket.md +160 -156
- package/docs/integrations/matomo-analytics.md +188 -178
- package/docs/integrations/meta-pixel.md +188 -150
- package/docs/integrations/microsoft-clarity.md +163 -155
- package/docs/integrations/microsoft-uet.md +148 -154
- package/docs/integrations/mixpanel-analytics.md +155 -160
- package/docs/integrations/one-dollar-stats.md +166 -165
- package/docs/integrations/openai-pixel.md +204 -301
- package/docs/integrations/overview.md +137 -80
- package/docs/integrations/pinterest-tag.md +191 -183
- package/docs/integrations/pirsch.md +169 -159
- package/docs/integrations/plausible-analytics.md +172 -158
- package/docs/integrations/posthog.md +226 -241
- package/docs/integrations/promptwatch.md +154 -154
- package/docs/integrations/reddit-pixel.md +185 -157
- package/docs/integrations/rudderstack.md +201 -186
- package/docs/integrations/rybbit-analytics.md +171 -160
- package/docs/integrations/segment.md +182 -154
- package/docs/integrations/snapchat-pixel.md +186 -156
- package/docs/integrations/tiktok-pixel.md +171 -150
- package/docs/integrations/umami-analytics.md +163 -158
- package/docs/integrations/vercel-analytics.md +166 -157
- package/docs/integrations/x-pixel.md +176 -150
- package/docs/integrations/youtube.md +121 -86
- package/docs/upgrade-v3.md +475 -467
- package/package.json +15 -9
- 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 -95
- package/docs/frameworks/react/script-loader.md +0 -69
- package/docs/frameworks/react/styling/overview.md +0 -266
- package/docs/guides/deployment-modes.md +0 -75
- package/docs/guides/shared-consent-controls.md +0 -158
- package/docs/integrations/clear-on-revocation.md +0 -167
- package/docs/integrations/granular-consent.md +0 -210
|
@@ -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.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Scripts
|
|
3
|
+
description: Load vendor scripts by consent category in a React app with
|
|
4
|
+
ConsentProvider, and clear stored data or reload the page when a visitor
|
|
5
|
+
withdraws consent.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Register vendor scripts
|
|
10
|
+
|
|
11
|
+
Pass vendor loaders to `ConsentProvider` through `options.scripts`. The
|
|
12
|
+
provider loads each script when its category is allowed and removes it when
|
|
13
|
+
the category is denied. The [quickstart](./quickstart.md)
|
|
14
|
+
registers PostHog and X Pixel this way:
|
|
15
|
+
|
|
16
|
+
```ts title="src/scripts.ts"
|
|
17
|
+
import { posthog } from '@c15t/integrations/posthog';
|
|
18
|
+
import { xPixel } from '@c15t/integrations/x-pixel';
|
|
19
|
+
|
|
20
|
+
export const scripts = [
|
|
21
|
+
posthog({
|
|
22
|
+
id: 'phc_your_project_key',
|
|
23
|
+
initOptions: { cookieless_mode: 'never' },
|
|
24
|
+
loadMode: 'after-consent',
|
|
25
|
+
}),
|
|
26
|
+
xPixel({ pixelId: 'your-pixel-id' }),
|
|
27
|
+
];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The provider in `src/consent.tsx` receives them:
|
|
31
|
+
|
|
32
|
+
```tsx title="src/consent.tsx"
|
|
33
|
+
import {
|
|
34
|
+
ConsentBanner,
|
|
35
|
+
ConsentDialog,
|
|
36
|
+
ConsentDialogLink,
|
|
37
|
+
ConsentProvider,
|
|
38
|
+
hosted,
|
|
39
|
+
} from 'c15t/react';
|
|
40
|
+
import type { ReactNode } from 'react';
|
|
41
|
+
|
|
42
|
+
import { scripts } from './scripts';
|
|
43
|
+
|
|
44
|
+
import 'c15t/react/styles.css';
|
|
45
|
+
|
|
46
|
+
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
47
|
+
|
|
48
|
+
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
49
|
+
<ConsentProvider options={{ mode, scripts }}>
|
|
50
|
+
{children}
|
|
51
|
+
<ConsentBanner />
|
|
52
|
+
<ConsentDialog />
|
|
53
|
+
<footer>
|
|
54
|
+
<ConsentDialogLink>Privacy settings</ConsentDialogLink>
|
|
55
|
+
</footer>
|
|
56
|
+
</ConsentProvider>
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each helper from `@c15t/integrations` sets its own category and a stable `id`.
|
|
61
|
+
Remove every other loader for the same vendor, such as a `<script>` tag in
|
|
62
|
+
`index.html` or an SDK you initialize at module level, so the vendor loads
|
|
63
|
+
once and only through c15t. [Integrations](../../integrations/overview.md) lists
|
|
64
|
+
every helper, and [building integrations](../../integrations/building-integrations.md)
|
|
65
|
+
covers a vendor without one.
|
|
66
|
+
|
|
67
|
+
## What happens when consent changes
|
|
68
|
+
|
|
69
|
+
* **Allowed.** The script loads, or its SDK is started, the first time its
|
|
70
|
+
category is allowed.
|
|
71
|
+
* **Denied later.** The provider removes the script element. Code that already
|
|
72
|
+
ran keeps running, so after the visitor turns off a category they had
|
|
73
|
+
allowed, the provider reloads the page once the save finishes. Set
|
|
74
|
+
`reloadOnConsentRevoked: false` only if every gated vendor stops itself.
|
|
75
|
+
* **`alwaysLoad` helpers.** Some integrations, such as Google Consent Mode,
|
|
76
|
+
load before consent and pass the visitor's choice to the vendor. Read the
|
|
77
|
+
vendor's guide; a category on a script does not always mean zero requests.
|
|
78
|
+
|
|
79
|
+
## Embeds and other requests
|
|
80
|
+
|
|
81
|
+
Scripts cover vendor code c15t loads for you. For the rest:
|
|
82
|
+
|
|
83
|
+
* [Embeds](./embeds.md) keeps iframes out of the page with
|
|
84
|
+
`ConsentGate` or the iframe blocker.
|
|
85
|
+
* [Network blocker](./network-blocker.md) holds `fetch` and
|
|
86
|
+
XHR calls that match a rule until their category is allowed.
|
|
87
|
+
|
|
88
|
+
## Clear stored data after revocation
|
|
89
|
+
|
|
90
|
+
Removing a script does not delete the cookies or storage entries it wrote. Add
|
|
91
|
+
`clearOnRevocation` to the provider options to delete the entries you list for
|
|
92
|
+
each category when it is denied. The option is read once, when the provider
|
|
93
|
+
mounts. [Clear on revocation](./clear-on-revocation.md) covers
|
|
94
|
+
configuration and browser limits.
|
|
95
|
+
|
|
96
|
+
## Let visitors turn off one vendor
|
|
97
|
+
|
|
98
|
+
A visitor can allow marketing and still switch off one vendor in it. Pass
|
|
99
|
+
`vendors` to the provider options. Helpers from `@c15t/integrations` already carry
|
|
100
|
+
their vendor slug. See [vendor consent](./vendor-consent.md).
|
|
101
|
+
|
|
102
|
+
To send your own events only to allowed integrations, see
|
|
103
|
+
[send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations).
|
|
104
|
+
|
|
105
|
+
## Check the scripts
|
|
106
|
+
|
|
107
|
+
Open DevTools Network in a private window, filter by each vendor's domain and
|
|
108
|
+
reload.
|
|
109
|
+
|
|
110
|
+
1. Before a choice under an opt-in policy, no vendor script loads.
|
|
111
|
+
2. Allow one category. Only that category's vendors load.
|
|
112
|
+
3. Turn the category off. The page reloads and the vendor stays absent.
|
|
113
|
+
4. Reload again. The rejection holds.
|
|
114
|
+
|
|
115
|
+
[Verify consent](../../guides/verify-consent.md) has the full checklist.
|