@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.
Files changed (183) hide show
  1. package/AGENTS.md +91 -72
  2. package/README.md +21 -17
  3. package/SKILL.md +34 -0
  4. package/dist/component-hooks/use-headless-consent-ui.js +1 -1
  5. package/dist/components/consent-gate/atoms.js +1 -1
  6. package/dist/components/consent-gate/consent-gate.js +1 -1
  7. package/dist/components/iab-panel/atoms/card.js +1 -1
  8. package/dist/components/iab-panel/atoms/feature-item.js +2 -0
  9. package/dist/components/iab-panel/atoms/header.js +1 -1
  10. package/dist/components/iab-panel/iab-panel.js +1 -1
  11. package/dist/components/iab-panel/index.js +1 -1
  12. package/dist/components/iab-prompt/atoms/overlay.js +1 -1
  13. package/dist/components/iab-prompt/atoms/root.js +1 -1
  14. package/dist/components/panel/atoms/card.js +1 -1
  15. package/dist/components/panel/atoms/root.js +1 -1
  16. package/dist/components/panel-trigger/atoms/button.js +1 -1
  17. package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
  18. package/dist/components/preferences/atoms/accordion.js +1 -1
  19. package/dist/components/prompt/atoms/overlay.js +1 -1
  20. package/dist/components/prompt/atoms/root.js +1 -1
  21. package/dist/components/prompt/components.js +1 -1
  22. package/dist/components/shared/libs/compose-refs.js +1 -0
  23. package/dist/components/shared/libs/slot.js +1 -1
  24. package/dist/components/shared/primitives/button.js +1 -1
  25. package/dist/components/shared/ui/dialog/dialog.js +1 -1
  26. package/dist/components/shared/ui/icon.js +1 -1
  27. package/dist/context/consent-tracking-context.js +1 -1
  28. package/dist/devtools.js +1 -1
  29. package/dist/draft.js +1 -1
  30. package/dist/hooks/use-focus-trap.js +1 -1
  31. package/dist/hooks.js +1 -1
  32. package/dist/iab/styles.tw3.css +4 -4
  33. package/dist/index.js +1 -1
  34. package/dist/module-hooks/iframe-blocker.js +1 -1
  35. package/dist/postcss-tailwind3.js +1 -0
  36. package/dist/provider.js +1 -1
  37. package/dist/server/fetch-ssr-data.js +1 -1
  38. package/dist/server/headers.js +1 -1
  39. package/dist/server/normalize-url.js +1 -1
  40. package/dist/styles.tw3.css +16 -16
  41. package/dist/transports/offline.js +1 -1
  42. package/dist/ui-save.js +1 -1
  43. package/dist/utils/merge-slot-props.js +1 -1
  44. package/dist/utils/theme-utils.js +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
  47. package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
  48. package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
  49. package/dist-types/components/iab-panel/index.d.ts +5 -2
  50. package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
  51. package/dist-types/components/iab-prompt/iab-prompt.d.ts +2 -2
  52. package/dist-types/components/iab-prompt/index.d.ts +2 -2
  53. package/dist-types/components/panel/atoms/card.d.ts +1 -0
  54. package/dist-types/components/prompt/components.d.ts +6 -0
  55. package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
  56. package/dist-types/components/shared/ui/icon.d.ts +2 -1
  57. package/dist-types/context/consent-tracking-context.d.ts +10 -0
  58. package/dist-types/context/theme-context.d.ts +2 -2
  59. package/dist-types/context.d.ts +6 -0
  60. package/dist-types/devtools.d.ts +13 -2
  61. package/dist-types/draft.d.ts +10 -4
  62. package/dist-types/hooks/use-focus-trap.d.ts +3 -1
  63. package/dist-types/hooks.d.ts +54 -13
  64. package/dist-types/iab.d.ts +1 -1
  65. package/dist-types/index.d.ts +2 -2
  66. package/dist-types/postcss-tailwind3.d.ts +21 -0
  67. package/dist-types/provider.d.ts +22 -5
  68. package/dist-types/server/headers.d.ts +14 -4
  69. package/dist-types/server/normalize-url.d.ts +14 -7
  70. package/dist-types/server/types.d.ts +12 -1
  71. package/dist-types/transports/offline.d.ts +7 -16
  72. package/dist-types/ui-config-context.d.ts +7 -1
  73. package/dist-types/utils/merge-slot-props.d.ts +15 -1
  74. package/dist-types/utils/theme-utils.d.ts +17 -2
  75. package/dist-types/version.d.ts +1 -1
  76. package/docs/README.md +91 -72
  77. package/docs/assets/v3/bottom-bar.png +0 -0
  78. package/docs/assets/v3/brand-card.png +0 -0
  79. package/docs/assets/v3/brand-preferences.png +0 -0
  80. package/docs/assets/v3/choice-wall.png +0 -0
  81. package/docs/assets/v3/headless-bar-html.png +0 -0
  82. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  83. package/docs/assets/v3/headless-bar.png +0 -0
  84. package/docs/assets/v3/slim-bar.png +0 -0
  85. package/docs/concepts/choose-your-setup.md +87 -0
  86. package/docs/{frameworks/react/concepts → concepts}/consent-categories.md +22 -7
  87. package/docs/{guides → concepts}/consent-state.md +71 -101
  88. package/docs/{guides → concepts}/data-fetching.md +31 -27
  89. package/docs/concepts/how-consent-works.md +123 -0
  90. package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +7 -59
  91. package/docs/customization/class-names.md +202 -0
  92. package/docs/customization/dark-mode.md +157 -0
  93. package/docs/customization/motion.md +119 -0
  94. package/docs/customization/overview.md +67 -34
  95. package/docs/customization/recipes.md +839 -49
  96. package/docs/customization/slots.md +216 -35
  97. package/docs/customization/stylesheets.md +147 -0
  98. package/docs/customization/tailwind.md +842 -0
  99. package/docs/customization/tokens.md +163 -96
  100. package/docs/customization/translations.md +61 -3
  101. package/docs/frameworks/react/callbacks.md +118 -0
  102. package/docs/frameworks/react/clear-on-revocation.md +106 -0
  103. package/docs/frameworks/react/components/consent-banner.md +42 -28
  104. package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
  105. package/docs/frameworks/react/components/consent-dialog-trigger.md +81 -76
  106. package/docs/frameworks/react/components/consent-dialog.md +56 -50
  107. package/docs/frameworks/react/components/consent-gate.md +37 -20
  108. package/docs/frameworks/react/components/consent-provider.md +126 -0
  109. package/docs/frameworks/react/components/consent-widget.md +23 -20
  110. package/docs/frameworks/react/components/dev-tools.md +52 -84
  111. package/docs/frameworks/react/components.md +105 -0
  112. package/docs/frameworks/react/compose.md +354 -0
  113. package/docs/frameworks/react/content-security-policy.md +126 -0
  114. package/docs/frameworks/react/customize.md +250 -0
  115. package/docs/frameworks/react/embeds.md +89 -0
  116. package/docs/frameworks/react/{hooks/overview.md → hooks.md} +21 -16
  117. package/docs/frameworks/react/{iab/overview.md → iab.md} +80 -31
  118. package/docs/frameworks/react/network-blocker.md +35 -41
  119. package/docs/frameworks/react/quickstart.md +139 -101
  120. package/docs/frameworks/react/rendering.md +128 -0
  121. package/docs/frameworks/react/scripts.md +115 -0
  122. package/docs/frameworks/react/translations.md +155 -0
  123. package/docs/frameworks/react/troubleshooting.md +92 -47
  124. package/docs/frameworks/react/vendor-consent.md +170 -0
  125. package/docs/guides/banner-experiments.md +654 -0
  126. package/docs/guides/troubleshooting.md +120 -47
  127. package/docs/guides/verify-consent.md +81 -49
  128. package/docs/integrations/adobe-analytics.md +167 -159
  129. package/docs/integrations/ahrefs-analytics.md +152 -154
  130. package/docs/integrations/amplitude.md +162 -156
  131. package/docs/integrations/building-integrations.md +136 -37
  132. package/docs/integrations/clearbit.md +154 -154
  133. package/docs/integrations/cloudflare-web-analytics.md +156 -156
  134. package/docs/integrations/cloudflare-zaraz.md +209 -261
  135. package/docs/integrations/crisp.md +164 -158
  136. package/docs/integrations/databuddy.md +157 -173
  137. package/docs/integrations/fathom-analytics.md +158 -156
  138. package/docs/integrations/front-chat.md +167 -167
  139. package/docs/integrations/google-maps.md +118 -83
  140. package/docs/integrations/google-tag-manager.md +178 -163
  141. package/docs/integrations/google-tag.md +163 -160
  142. package/docs/integrations/heap.md +163 -155
  143. package/docs/integrations/hightouch.md +161 -157
  144. package/docs/integrations/hotjar.md +159 -155
  145. package/docs/integrations/intercom.md +183 -153
  146. package/docs/integrations/klaviyo.md +486 -0
  147. package/docs/integrations/linkedin-insights.md +174 -150
  148. package/docs/integrations/logrocket.md +160 -156
  149. package/docs/integrations/matomo-analytics.md +188 -178
  150. package/docs/integrations/meta-pixel.md +188 -150
  151. package/docs/integrations/microsoft-clarity.md +163 -155
  152. package/docs/integrations/microsoft-uet.md +148 -154
  153. package/docs/integrations/mixpanel-analytics.md +155 -160
  154. package/docs/integrations/one-dollar-stats.md +166 -165
  155. package/docs/integrations/openai-pixel.md +204 -301
  156. package/docs/integrations/overview.md +137 -80
  157. package/docs/integrations/pinterest-tag.md +191 -183
  158. package/docs/integrations/pirsch.md +169 -159
  159. package/docs/integrations/plausible-analytics.md +172 -158
  160. package/docs/integrations/posthog.md +226 -241
  161. package/docs/integrations/promptwatch.md +154 -154
  162. package/docs/integrations/reddit-pixel.md +185 -157
  163. package/docs/integrations/rudderstack.md +201 -186
  164. package/docs/integrations/rybbit-analytics.md +171 -160
  165. package/docs/integrations/segment.md +182 -154
  166. package/docs/integrations/snapchat-pixel.md +186 -156
  167. package/docs/integrations/tiktok-pixel.md +171 -150
  168. package/docs/integrations/umami-analytics.md +163 -158
  169. package/docs/integrations/vercel-analytics.md +166 -157
  170. package/docs/integrations/x-pixel.md +176 -150
  171. package/docs/integrations/youtube.md +121 -86
  172. package/docs/upgrade-v3.md +475 -467
  173. package/package.json +15 -9
  174. package/docs/assets/v3/brand-bar.png +0 -0
  175. package/docs/assets/v3/mobile-card.png +0 -0
  176. package/docs/assets/v3/preferences.png +0 -0
  177. package/docs/frameworks/react/components/consent-manager-provider.md +0 -95
  178. package/docs/frameworks/react/script-loader.md +0 -69
  179. package/docs/frameworks/react/styling/overview.md +0 -266
  180. package/docs/guides/deployment-modes.md +0 -75
  181. package/docs/guides/shared-consent-controls.md +0 -158
  182. package/docs/integrations/clear-on-revocation.md +0 -167
  183. package/docs/integrations/granular-consent.md +0 -210
@@ -1,143 +1,181 @@
1
1
  ---
2
2
  title: Quickstart
3
- description: Connect a React application to Inth and add consent UI, persistence
4
- and a preferences link.
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
- ## Connect Inth
8
+ ## Before you start
9
9
 
10
- [Create an Inth project](https://inth.com), configure policy rules and the app's
11
- allowed origin, and copy its backend endpoint. This guide uses a client-rendered
12
- React app with Vite. Applications with a framework server should use that
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
- | Package manager | Command |
16
- | :-------------- | :------------------------------- |
17
- | npm | `npm install c15t @c15t/scripts` |
18
- | pnpm | `pnpm add c15t @c15t/scripts` |
19
- | yarn | `yarn add c15t @c15t/scripts` |
20
- | bun | `bun add c15t @c15t/scripts` |
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
- Set `VITE_C15T_BACKEND_URL` to the endpoint from Inth. Vite embeds public values
23
- at build time, so rebuild when changing it. Keep management credentials out of
24
- public variables.
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
- ## Register consent-gated scripts
29
+ ## Install c15t
27
30
 
28
- Include measurement and marketing categories in your policy for these vendors.
29
- Remove any existing loaders for them so each script loads once.
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
- This setup uses Vite's public environment variables. Set the Inth endpoint and
32
- add your PostHog project key, PostHog API host and X Pixel ID in `.env.local`:
38
+ `c15t` contains the React adapter at `c15t/react`. `@c15t/integrations` holds the
39
+ vendor loaders used below.
33
40
 
34
- ```dotenv title=".env.local"
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 with
47
- `loadMode: 'after-consent'` and disables cookieless capture with
48
- `cookieless_mode: 'never'`. X Pixel waits for marketing permission.
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/scripts/posthog';
52
- import { xPixel } from '@c15t/scripts/x-pixel';
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
- ...(posthogKey
59
- ? [
60
- posthog({
61
- apiHost: import.meta.env.VITE_POSTHOG_HOST,
62
- id: posthogKey,
63
- initOptions: { cookieless_mode: 'never' },
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
- ## Mount the provider and UI
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
- ConsentBanner,
78
- ConsentDialog,
79
- ConsentDialogLink,
80
- ConsentProvider,
81
- hosted,
80
+ ConsentBanner,
81
+ ConsentDialog,
82
+ ConsentDialogLink,
83
+ ConsentProvider,
84
+ hosted,
82
85
  } from 'c15t/react';
83
- import 'c15t/react/styles.css';
86
+ import type { ReactNode } from 'react';
87
+
84
88
  import { scripts } from './scripts';
85
89
 
86
- const backendURL = import.meta.env.VITE_C15T_BACKEND_URL;
87
- if (!backendURL) throw new Error('Set VITE_C15T_BACKEND_URL');
88
- const mode = hosted({ url: backendURL });
89
-
90
- export function Consent({ children }: { children: ReactNode }) {
91
- return (
92
- <ConsentProvider options={{ mode, scripts }}>
93
- {children}
94
- <ConsentBanner />
95
- <ConsentDialog />
96
- <footer><ConsentDialogLink>Privacy settings</ConsentDialogLink></footer>
97
- </ConsentProvider>
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
- import App from './App';
113
+
114
+ import { App } from './app';
105
115
  import { Consent } from './consent';
106
116
 
107
117
  const root = document.getElementById('root');
108
- if (!root) throw new Error('Missing #root element');
109
- createRoot(root).render(<Consent><App /></Consent>);
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 across navigation. The banner appears when the resolved
113
- policy requires a prompt; not every region or returning visitor needs one.
114
- The dialog link keeps preferences reachable after the prompt closes.
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
- ## Gate optional functionality
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 MarketingContent() {
122
- return useConsent('marketing') ? <div>Optional marketing content</div> : null;
140
+ export function MarketingBanner() {
141
+ return useConsent('marketing') ? <aside>Spring sale</aside> : null;
123
142
  }
124
143
  ```
125
144
 
126
- This controls the rendered component. Move vendor SDKs into the
127
- [script loader](./script-loader.md) too. An independently
128
- loaded analytics script does not wait merely because a consent provider exists.
129
-
130
- `useConsent` returns effective permission, not proof of an explicit grant.
131
- Read [consent state](../../guides/consent-state.md) when recording choice events.
132
-
133
- ## Verify and customize
134
-
135
- Reject under an opt-in policy, reload and confirm optional requests stay blocked.
136
- Reopen preferences, save a different choice and test navigation. Follow
137
- [verification](../../guides/verify-consent.md), then
138
- [customization](../../customization/overview.md) to apply your design.
139
-
140
- ## Run the complete example
141
-
142
- Try the [runnable example](https://c15t.com/docs/examples) with PostHog, X Pixel, a consent-gated
143
- YouTube video and a persistent preferences control. Use it to test rejection, permission changes and reloads.
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.