@c15t/nextjs 2.0.0-rc.0 → 2.0.0-rc.10
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/README.md +10 -3
- package/client/components/consent-dialog-link.js +3 -0
- package/dist/headless.cjs +1 -1
- package/dist/iab/styles.css +12 -0
- package/dist/iab/styles.tw3.css +14 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/libs/browser-initial-data.cjs +1 -0
- package/dist/libs/browser-initial-data.js +1 -0
- package/dist/libs/initial-data.cjs +1 -1
- package/dist/libs/initial-data.js +1 -1
- package/dist/styles.css +10 -0
- package/dist/styles.tw3.css +13 -0
- package/dist/types.cjs +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/{dist → dist-types}/headless.d.ts +0 -1
- package/{dist → dist-types}/index.d.ts +3 -2
- package/dist-types/libs/browser-initial-data.d.ts +9 -0
- package/{dist → dist-types}/libs/initial-data.d.ts +7 -2
- package/dist-types/types.d.ts +38 -0
- package/dist-types/version.d.ts +1 -0
- package/docs/README.md +73 -0
- package/docs/building-headless-components.md +377 -0
- package/docs/callbacks.md +184 -0
- package/docs/components/consent-banner.md +269 -0
- package/docs/components/consent-dialog-link.md +59 -0
- package/docs/components/consent-dialog-trigger.md +103 -0
- package/docs/components/consent-dialog.md +177 -0
- package/docs/components/consent-manager-provider.md +425 -0
- package/docs/components/consent-widget.md +133 -0
- package/docs/components/dev-tools.md +63 -0
- package/docs/components/frame.md +73 -0
- package/docs/concepts/client-modes.md +175 -0
- package/docs/concepts/consent-categories.md +97 -0
- package/docs/concepts/consent-models.md +116 -0
- package/docs/concepts/cookie-management.md +122 -0
- package/docs/concepts/glossary.md +23 -0
- package/docs/concepts/initialization-flow.md +148 -0
- package/docs/concepts/policy-packs.md +229 -0
- package/docs/headless.md +190 -0
- package/docs/hooks/use-color-scheme.md +40 -0
- package/docs/hooks/use-consent-manager/checking-consent.md +94 -0
- package/docs/hooks/use-consent-manager/location-info.md +95 -0
- package/docs/hooks/use-consent-manager/overview.md +420 -0
- package/docs/hooks/use-consent-manager/setting-consent.md +92 -0
- package/docs/hooks/use-draggable.md +57 -0
- package/docs/hooks/use-focus-trap.md +41 -0
- package/docs/hooks/use-reduced-motion.md +35 -0
- package/docs/hooks/use-ssr-status.md +31 -0
- package/docs/hooks/use-text-direction.md +49 -0
- package/docs/hooks/use-translations.md +118 -0
- package/docs/iab/consent-banner.md +94 -0
- package/docs/iab/consent-dialog.md +134 -0
- package/docs/iab/overview.md +126 -0
- package/docs/iab/use-gvl-data.md +20 -0
- package/docs/iframe-blocking.md +107 -0
- package/docs/integrations/building-integrations.md +405 -0
- package/docs/integrations/databuddy.md +203 -0
- package/docs/integrations/google-tag-manager.md +153 -0
- package/docs/integrations/google-tag.md +122 -0
- package/docs/integrations/linkedin-insights.md +109 -0
- package/docs/integrations/meta-pixel.md +342 -0
- package/docs/integrations/microsoft-uet.md +112 -0
- package/docs/integrations/overview.md +105 -0
- package/docs/integrations/posthog.md +199 -0
- package/docs/integrations/tiktok-pixel.md +113 -0
- package/docs/integrations/x-pixel.md +143 -0
- package/docs/internationalization.md +197 -0
- package/docs/network-blocker.md +178 -0
- package/docs/optimization.md +234 -0
- package/docs/policy-packs.md +246 -0
- package/docs/quickstart.md +161 -0
- package/docs/script-loader.md +321 -0
- package/docs/server-side.md +176 -0
- package/docs/styling/classnames.md +92 -0
- package/docs/styling/color-scheme.md +82 -0
- package/docs/styling/css-variables.md +92 -0
- package/docs/styling/overview.md +456 -0
- package/docs/styling/slots.md +127 -0
- package/docs/styling/tailwind.md +113 -0
- package/docs/styling/tokens.md +216 -0
- package/docs/troubleshooting.md +146 -0
- package/iab/styles.css +1 -0
- package/package.json +38 -17
- package/readme.json +4 -0
- package/src/iab/styles.css +12 -0
- package/src/iab/styles.tw3.css +14 -0
- package/src/styles.css +10 -0
- package/src/styles.tw3.css +13 -0
- package/styles.css +1 -0
- package/dist/headless.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/libs/initial-data.d.ts.map +0 -1
- package/dist/types.d.ts +0 -16
- package/dist/types.d.ts.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Network Blocker
|
|
3
|
+
description: Block outgoing network requests to third-party domains until the user grants consent for the appropriate category.
|
|
4
|
+
---
|
|
5
|
+
The network blocker intercepts outgoing `fetch` and `XMLHttpRequest` calls and blocks them based on consent state and domain rules. This catches tracking requests that happen outside of script loading - for example, beacon calls, API requests to analytics endpoints, or pixel fires from already-loaded scripts.
|
|
6
|
+
|
|
7
|
+
## Configuration
|
|
8
|
+
|
|
9
|
+
Add `networkBlocker` to your provider options:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { type ReactNode } from 'react';
|
|
13
|
+
import { ConsentManagerProvider } from '@c15t/nextjs';
|
|
14
|
+
|
|
15
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
16
|
+
return (
|
|
17
|
+
<ConsentManagerProvider
|
|
18
|
+
options={{
|
|
19
|
+
mode: 'hosted',
|
|
20
|
+
backendURL: '/api/c15t',
|
|
21
|
+
networkBlocker: {
|
|
22
|
+
rules: [
|
|
23
|
+
{
|
|
24
|
+
id: 'google-analytics',
|
|
25
|
+
domain: 'google-analytics.com',
|
|
26
|
+
category: 'measurement',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'facebook-pixel',
|
|
30
|
+
domain: 'facebook.com',
|
|
31
|
+
pathIncludes: '/tr',
|
|
32
|
+
category: 'marketing',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'analytics-post',
|
|
36
|
+
domain: 'analytics.example.com',
|
|
37
|
+
methods: ['POST'],
|
|
38
|
+
category: 'measurement',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
</ConsentManagerProvider>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Rule Matching
|
|
51
|
+
|
|
52
|
+
Rules match requests using three criteria:
|
|
53
|
+
|
|
54
|
+
### Domain matching
|
|
55
|
+
|
|
56
|
+
The `domain` field matches the request hostname. Subdomains are automatically included - a rule for `google-analytics.com` also matches `www.google-analytics.com` and `stats.google-analytics.com`.
|
|
57
|
+
|
|
58
|
+
### Path matching
|
|
59
|
+
|
|
60
|
+
The optional `pathIncludes` field requires the request URL path to contain the specified substring. This lets you target specific endpoints without blocking the entire domain.
|
|
61
|
+
|
|
62
|
+
### Method matching
|
|
63
|
+
|
|
64
|
+
The optional `methods` array restricts the rule to specific HTTP methods. If omitted, the rule applies to all methods.
|
|
65
|
+
|
|
66
|
+
## Consent Conditions
|
|
67
|
+
|
|
68
|
+
Like the script loader, `category` accepts a `HasCondition`:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// Simple
|
|
72
|
+
{ category: 'measurement' }
|
|
73
|
+
|
|
74
|
+
// Must have both
|
|
75
|
+
{ category: { and: ['measurement', 'marketing'] } }
|
|
76
|
+
|
|
77
|
+
// Must have either
|
|
78
|
+
{ category: { or: ['measurement', 'marketing'] } }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Monitoring Blocked Requests
|
|
82
|
+
|
|
83
|
+
### Console logging
|
|
84
|
+
|
|
85
|
+
Blocked requests are logged to the console by default. Disable with `logBlockedRequests: false`.
|
|
86
|
+
|
|
87
|
+
### Callback
|
|
88
|
+
|
|
89
|
+
Use `onRequestBlocked` to handle blocked requests programmatically:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
networkBlocker: {
|
|
93
|
+
rules: [...],
|
|
94
|
+
onRequestBlocked: ({ method, url, rule }) => {
|
|
95
|
+
console.log(`Blocked ${method} ${url} (rule: ${rule?.id})`);
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Runtime Updates
|
|
101
|
+
|
|
102
|
+
Update the network blocker configuration at runtime:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { useConsentManager } from '@c15t/nextjs';
|
|
106
|
+
|
|
107
|
+
function NetworkBlockerManager() {
|
|
108
|
+
const { setNetworkBlocker } = useConsentManager();
|
|
109
|
+
|
|
110
|
+
const addRule = () => {
|
|
111
|
+
setNetworkBlocker({
|
|
112
|
+
rules: [
|
|
113
|
+
{
|
|
114
|
+
id: 'new-tracker',
|
|
115
|
+
domain: 'tracker.example.com',
|
|
116
|
+
category: 'marketing',
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## API Reference
|
|
125
|
+
|
|
126
|
+
### NetworkBlockerRule
|
|
127
|
+
|
|
128
|
+
|Property|Type|Description|Default|Required|
|
|
129
|
+
|:--|:--|:--|:--|:--:|
|
|
130
|
+
|id|string \|undefined|Optional identifier for the rule. Useful for debugging and logging.|-|Optional|
|
|
131
|
+
|domain|string|Domain that this rule applies to.|-|✅ Required|
|
|
132
|
+
|pathIncludes|string \|undefined|Optional path substring that must be present in the request path for the rule to apply.|-|Optional|
|
|
133
|
+
|methods|string\[] \|undefined|Optional list of HTTP methods that this rule applies to. If omitted, the rule applies to all methods.|-|Optional|
|
|
134
|
+
|category|HasCondition\<AllConsentNames>|Consent condition that must be satisfied to allow the request. When this condition is not satisfied, matching requests will be blocked.|-|✅ Required|
|
|
135
|
+
|
|
136
|
+
### NetworkBlockerConfig
|
|
137
|
+
|
|
138
|
+
|Property|Type|Description|Default|Required|
|
|
139
|
+
|:--|:--|:--|:--|:--:|
|
|
140
|
+
|enabled|boolean \|undefined|Whether the network blocker is enabled.|-|Optional|
|
|
141
|
+
|initialConsents|ConsentState \|undefined|The consent state snapshot that is currently used for blocking logic.|-|Optional|
|
|
142
|
+
|logBlockedRequests|boolean \|undefined|Whether to automatically log blocked requests to the console.|-|Optional|
|
|
143
|
+
|onRequestBlocked|Object \|undefined|Callback invoked whenever a request is blocked.|-|Optional|
|
|
144
|
+
|rules|NetworkBlockerRule|Domain rules that determine which requests should be blocked.|-|✅ Required|
|
|
145
|
+
|
|
146
|
+
#### `initialConsents` ConsentState
|
|
147
|
+
|
|
148
|
+
The consent state snapshot that is currently used for blocking logic.
|
|
149
|
+
|
|
150
|
+
|Property|Type|Description|Default|Required|
|
|
151
|
+
|:--|:--|:--|:--|:--:|
|
|
152
|
+
|experience|boolean|-|-|✅ Required|
|
|
153
|
+
|functionality|boolean|-|-|✅ Required|
|
|
154
|
+
|marketing|boolean|-|-|✅ Required|
|
|
155
|
+
|measurement|boolean|-|-|✅ Required|
|
|
156
|
+
|necessary|boolean|-|-|✅ Required|
|
|
157
|
+
|
|
158
|
+
#### `onRequestBlocked`
|
|
159
|
+
|
|
160
|
+
Callback invoked whenever a request is blocked.
|
|
161
|
+
|
|
162
|
+
|Property|Type|Description|Default|Required|
|
|
163
|
+
|:--|:--|:--|:--|:--:|
|
|
164
|
+
|method|string|The HTTP method of the blocked request (e.g. GET, POST).|-|✅ Required|
|
|
165
|
+
|url|string|The URL of the blocked request.|-|✅ Required|
|
|
166
|
+
|rule|NetworkBlockerRule \|undefined|The rule that caused the request to be blocked, if available. Can be undefined when the blocker configuration changed between evaluation and logging.|-|Optional|
|
|
167
|
+
|
|
168
|
+
#### `rules` NetworkBlockerRule
|
|
169
|
+
|
|
170
|
+
Domain rules that determine which requests should be blocked.
|
|
171
|
+
|
|
172
|
+
|Property|Type|Description|Default|Required|
|
|
173
|
+
|:--|:--|:--|:--|:--:|
|
|
174
|
+
|id|string \|undefined|Optional identifier for the rule. Useful for debugging and logging.|-|Optional|
|
|
175
|
+
|domain|string|Domain that this rule applies to.|-|✅ Required|
|
|
176
|
+
|pathIncludes|string \|undefined|Optional path substring that must be present in the request path for the rule to apply.|-|Optional|
|
|
177
|
+
|methods|string\[] \|undefined|Optional list of HTTP methods that this rule applies to. If omitted, the rule applies to all methods.|-|Optional|
|
|
178
|
+
|category|HasCondition\<AllConsentNames>|Consent condition that must be satisfied to allow the request. When this condition is not satisfied, matching requests will be blocked.|-|✅ Required|
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Optimization
|
|
3
|
+
description: Improve c15t startup performance in Next.js with same-origin rewrites, static prefetching, and dynamic-route SSR.
|
|
4
|
+
lastModified: 2026-04-14
|
|
5
|
+
---
|
|
6
|
+
Use this guide when you care about banner visibility speed, route static-ness, and reducing backend round-trip cost.
|
|
7
|
+
|
|
8
|
+
## Start Here
|
|
9
|
+
|
|
10
|
+
Apply the optimizations in this order:
|
|
11
|
+
|
|
12
|
+
|Situation|Use|Why|
|
|
13
|
+
|--|--|--|
|
|
14
|
+
|Any production Next.js app|Same-origin `/api/c15t` rewrite|Lowers browser startup overhead and keeps the backend origin out of client config|
|
|
15
|
+
|Static route, but banner speed matters|`C15tPrefetch`|Starts `/init` before hydration without making the route dynamic|
|
|
16
|
+
|Dynamic route, or you want the fastest first banner|`fetchInitialData()`|Starts `/init` on the server and streams the result into the provider|
|
|
17
|
+
|You want the simplest setup|Client-only init|No extra moving parts, but the banner appears later on cold loads|
|
|
18
|
+
|
|
19
|
+
> ℹ️ **Info:**
|
|
20
|
+
> C15tPrefetch is the only static-route prefetch step you need in @c15t/nextjs. Matching prefetched data is consumed automatically during first initialization.
|
|
21
|
+
>
|
|
22
|
+
> ℹ️ **Info:**
|
|
23
|
+
> Prefetched or SSR data is reused only when the request context still matches at runtime. That includes the backend URL, credentials, overrides, and the browser's ambient GPC signal.
|
|
24
|
+
|
|
25
|
+
In production benchmarks with a same-origin rewrite, prefetching strategies show measurable improvement over client-only init:
|
|
26
|
+
|
|
27
|
+
|Strategy|Scripts loaded|Data request starts|Banner visible|
|
|
28
|
+
|--|--|--|--|
|
|
29
|
+
|Client-only (no prefetch)|baseline|baseline|baseline|
|
|
30
|
+
|Browser prefetch|\~1.3x faster|\~2.6x earlier|\~1.25x faster|
|
|
31
|
+
|Server prefetch|\~2x faster|before page loads|\~1.9x faster|
|
|
32
|
+
|
|
33
|
+
## 1) Prefer Same-Origin Rewrites
|
|
34
|
+
|
|
35
|
+
Proxy c15t requests through your Next.js app so the browser calls your own origin instead of a third-party domain.
|
|
36
|
+
|
|
37
|
+
```ts title="next.config.ts"
|
|
38
|
+
import type { NextConfig } from 'next';
|
|
39
|
+
|
|
40
|
+
const config: NextConfig = {
|
|
41
|
+
async rewrites() {
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
source: '/api/c15t/:path*',
|
|
45
|
+
destination: `${process.env.NEXT_PUBLIC_C15T_URL}/:path*`,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default config;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then use:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<ConsentManagerProvider options={{ backendURL: '/api/c15t', mode: 'hosted' }}>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Why this helps:
|
|
61
|
+
|
|
62
|
+
* Same-origin requests avoid extra DNS/TLS setup in many deployments
|
|
63
|
+
* Ad blockers are less likely to block your init endpoint
|
|
64
|
+
* You can change backend infrastructure without touching client code
|
|
65
|
+
|
|
66
|
+
> ℹ️ **Info:**
|
|
67
|
+
> Set NEXT\_PUBLIC\_C15T\_URL in .env to your backend URL, for example https\://your-project.inth.app.
|
|
68
|
+
>
|
|
69
|
+
> ℹ️ **Info:**
|
|
70
|
+
> Use rewrites for browser-side calls (ConsentManagerProvider, C15tPrefetch). For server-side fetchInitialData(), prefer a direct backend URL (for example https\://your-project.inth.app) to avoid an extra server proxy hop.
|
|
71
|
+
|
|
72
|
+
## 2) Choose A Startup Strategy
|
|
73
|
+
|
|
74
|
+
Start with a same-origin rewrite and the default client-side provider. Add one of the preloading strategies below only when the route behavior or performance target calls for it.
|
|
75
|
+
|
|
76
|
+
### Client-Only Init
|
|
77
|
+
|
|
78
|
+
Keep the default provider setup when you want the least complexity. This works on both static and dynamic routes, but the banner only appears after the client runtime starts and the initial `/init` request completes.
|
|
79
|
+
|
|
80
|
+
### Dynamic Routes: Fetch On The Server And Stream
|
|
81
|
+
|
|
82
|
+
Use `fetchInitialData()` when the route is already dynamic, or when you are willing to make it dynamic in exchange for the fastest first banner.
|
|
83
|
+
|
|
84
|
+
Because it depends on `next/headers`, this opts the route into dynamic rendering.
|
|
85
|
+
|
|
86
|
+
```tsx title="app/layout.tsx"
|
|
87
|
+
import { fetchInitialData } from '@c15t/nextjs';
|
|
88
|
+
import ConsentManager from '@/components/consent-manager';
|
|
89
|
+
|
|
90
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
91
|
+
const ssrData = fetchInitialData({
|
|
92
|
+
backendURL: process.env.NEXT_PUBLIC_C15T_URL!,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<html lang="en">
|
|
97
|
+
<body>
|
|
98
|
+
<ConsentManager ssrData={ssrData}>{children}</ConsentManager>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```tsx title="components/consent-manager/provider.tsx"
|
|
106
|
+
'use client';
|
|
107
|
+
|
|
108
|
+
import { type ReactNode } from 'react';
|
|
109
|
+
import {
|
|
110
|
+
ConsentManagerProvider,
|
|
111
|
+
ConsentBanner,
|
|
112
|
+
ConsentDialog,
|
|
113
|
+
type InitialDataPromise,
|
|
114
|
+
} from '@c15t/nextjs';
|
|
115
|
+
|
|
116
|
+
export default function ConsentManager({
|
|
117
|
+
children,
|
|
118
|
+
ssrData,
|
|
119
|
+
}: {
|
|
120
|
+
children: ReactNode;
|
|
121
|
+
ssrData?: InitialDataPromise;
|
|
122
|
+
}) {
|
|
123
|
+
return (
|
|
124
|
+
<ConsentManagerProvider
|
|
125
|
+
options={{
|
|
126
|
+
mode: 'hosted',
|
|
127
|
+
backendURL: '/api/c15t',
|
|
128
|
+
ssrData,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<ConsentBanner />
|
|
132
|
+
<ConsentDialog />
|
|
133
|
+
{children}
|
|
134
|
+
</ConsentManagerProvider>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> ℹ️ **Info:**
|
|
140
|
+
> Do not await fetchInitialData(). Pass the unresolved Promise to the provider so Next.js can stream the route while /init runs in parallel.
|
|
141
|
+
>
|
|
142
|
+
> ℹ️ **Info:**
|
|
143
|
+
> For fetchInitialData(), prefer a direct backend URL such as https\://your-project.inth.app instead of a rewrite to avoid an extra server-side proxy hop. See Server-Side Data Fetching for the full flow.
|
|
144
|
+
|
|
145
|
+
### Static Routes: Start Fetch Early In The Browser
|
|
146
|
+
|
|
147
|
+
Use `C15tPrefetch` when the route needs to stay static but you still want the `/init` request to start before hydration. Matching prefetched data is consumed automatically by the runtime during first store initialization.
|
|
148
|
+
|
|
149
|
+
```tsx title="app/layout.tsx"
|
|
150
|
+
import { C15tPrefetch } from '@c15t/nextjs';
|
|
151
|
+
import { ConsentManager } from '@/components/consent-manager';
|
|
152
|
+
|
|
153
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
154
|
+
return (
|
|
155
|
+
<html lang="en">
|
|
156
|
+
<head>
|
|
157
|
+
<C15tPrefetch
|
|
158
|
+
backendURL="/api/c15t"
|
|
159
|
+
overrides={{ country: 'DE', region: 'BE', language: 'de' }}
|
|
160
|
+
/>
|
|
161
|
+
</head>
|
|
162
|
+
<body>
|
|
163
|
+
<ConsentManager>{children}</ConsentManager>
|
|
164
|
+
</body>
|
|
165
|
+
</html>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```tsx title="components/consent-manager/provider.tsx"
|
|
171
|
+
'use client';
|
|
172
|
+
|
|
173
|
+
import {
|
|
174
|
+
ConsentManagerProvider,
|
|
175
|
+
ConsentBanner,
|
|
176
|
+
ConsentDialog,
|
|
177
|
+
} from '@c15t/nextjs';
|
|
178
|
+
|
|
179
|
+
export default function ConsentManagerClient({ children }: { children: React.ReactNode }) {
|
|
180
|
+
return (
|
|
181
|
+
<ConsentManagerProvider
|
|
182
|
+
options={{
|
|
183
|
+
mode: 'hosted',
|
|
184
|
+
backendURL: '/api/c15t',
|
|
185
|
+
overrides: { country: 'DE', region: 'BE', language: 'de' },
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
<ConsentBanner />
|
|
189
|
+
<ConsentDialog />
|
|
190
|
+
{children}
|
|
191
|
+
</ConsentManagerProvider>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
> ℹ️ **Info:**
|
|
197
|
+
> C15tPrefetch uses Next.js beforeInteractive script loading, so the /init request can start before hydration.
|
|
198
|
+
>
|
|
199
|
+
> ℹ️ **Info:**
|
|
200
|
+
> If the request context changes between prefetch time and runtime, c15t falls back to a normal client /init. A common example is overrides.gpc conflicting with the browser's ambient GPC signal.
|
|
201
|
+
|
|
202
|
+
## Keep The Provider Mounted Across Navigation
|
|
203
|
+
|
|
204
|
+
Mount the consent provider at the app root so route transitions do not remount it.
|
|
205
|
+
|
|
206
|
+
Why this helps:
|
|
207
|
+
|
|
208
|
+
* Avoids re-running init work on client-side navigation
|
|
209
|
+
* Prevents extra callback churn from remount cycles
|
|
210
|
+
* Keeps banner/dialog state stable between route transitions
|
|
211
|
+
|
|
212
|
+
## Animation Performance
|
|
213
|
+
|
|
214
|
+
The default motion tokens are tuned for speed-first product UI:
|
|
215
|
+
|
|
216
|
+
|Token|Duration|Used for|
|
|
217
|
+
|--|--|--|
|
|
218
|
+
|`fast`|80ms|Banner slide + overlay, card scale, button hover, widget entry/exit|
|
|
219
|
+
|`normal`|150ms|Accordion, switch toggle|
|
|
220
|
+
|`slow`|200ms|Dialog trigger snap, tab indicator|
|
|
221
|
+
|
|
222
|
+
These defaults follow the principle that product UI should be fast and purposeful — animations exist for spatial continuity, not decoration. In benchmarks, animation duration contributes a constant floor to "data fetched → banner visible" timing. The default tokens sit at the lower end of standard UI ranges (80-200ms) to minimize that floor.
|
|
223
|
+
|
|
224
|
+
To customize motion durations and easing, see [Styling](/docs/frameworks/next/styling/overview).
|
|
225
|
+
|
|
226
|
+
## Reduce Network Overhead
|
|
227
|
+
|
|
228
|
+
If you must use a cross-origin backend URL, add preconnect so the browser starts DNS/TLS early:
|
|
229
|
+
|
|
230
|
+
```tsx title="app/layout.tsx"
|
|
231
|
+
<head>
|
|
232
|
+
<link rel="preconnect" href="https://your-project.inth.app" crossOrigin="" />
|
|
233
|
+
</head>
|
|
234
|
+
```
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Policy Packs
|
|
3
|
+
description: Configure regional consent policies in Next.js — hosted mode, presets, and offline fallback.
|
|
4
|
+
---
|
|
5
|
+
Policy packs configure how c15t handles regional consent — which model (opt-in, opt-out, none), which categories, and what UI to show. The backend resolves the right policy automatically based on the visitor's location.
|
|
6
|
+
|
|
7
|
+
**For most apps, you just need a `ConsentManagerProvider` pointing at your backend with presets configured there.** The frontend receives the resolved policy via the `/init` response — no client-side policy config required.
|
|
8
|
+
|
|
9
|
+
When a backend isn't available — local development, static previews, Storybook, automated tests, or as a resilience fallback during a temporary outage — you can pass policies directly to the provider via `offlinePolicy.policyPacks` and c15t resolves them locally.
|
|
10
|
+
|
|
11
|
+
> ℹ️ **Info:**
|
|
12
|
+
> For QA and testing, use the c15t DevTools to simulate different regions and policy responses against your real backend, rather than switching to offline mode.
|
|
13
|
+
|
|
14
|
+
## Hosted Mode (Recommended)
|
|
15
|
+
|
|
16
|
+
When using inth.com or a self-hosted backend, the provider connects automatically. No policy configuration is needed on the frontend:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<ConsentManagerProvider
|
|
20
|
+
options={{
|
|
21
|
+
backendURL: 'https://your-instance.c15t.dev',
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
<ConsentBanner />
|
|
25
|
+
<ConsentDialog />
|
|
26
|
+
{children}
|
|
27
|
+
</ConsentManagerProvider>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The backend resolves the correct policy based on the visitor's geo data and returns it in the `/init` response. Configure your presets on the backend side.
|
|
31
|
+
|
|
32
|
+
## Offline Presets (Development and Fallback)
|
|
33
|
+
|
|
34
|
+
Use offline presets mainly for local development, Storybook, deterministic tests, or temporary backend outages:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { policyPackPresets } from '@c15t/react';
|
|
38
|
+
|
|
39
|
+
offlinePolicy: {
|
|
40
|
+
i18n: {
|
|
41
|
+
defaultProfile: 'default',
|
|
42
|
+
messages: {
|
|
43
|
+
default: {
|
|
44
|
+
translations: {
|
|
45
|
+
en: { cookieBanner: { title: 'Privacy choices' } },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
eu: {
|
|
49
|
+
fallbackLanguage: 'en',
|
|
50
|
+
translations: {
|
|
51
|
+
en: { cookieBanner: { title: 'EU GDPR Consent' } },
|
|
52
|
+
fr: { cookieBanner: { title: 'Consentement RGPD' } },
|
|
53
|
+
de: { cookieBanner: { title: 'GDPR-Einwilligung' } },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
policyPacks: [
|
|
59
|
+
{
|
|
60
|
+
...policyPackPresets.europeOptIn(),
|
|
61
|
+
i18n: { messageProfile: 'eu' },
|
|
62
|
+
},
|
|
63
|
+
policyPackPresets.californiaOptOut(),
|
|
64
|
+
policyPackPresets.worldNoBanner(),
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Available presets:
|
|
70
|
+
|
|
71
|
+
|Preset|Model|Matches|
|
|
72
|
+
|--|--|--|
|
|
73
|
+
|`europeOptIn()`|`opt-in`|EEA + UK countries + geo fallback|
|
|
74
|
+
|`europeIab()`|`iab`|EEA + UK countries + geo fallback (TCF 2.3)|
|
|
75
|
+
|`californiaOptOut()`|`opt-out`|US-CA region|
|
|
76
|
+
|`quebecOptIn()`|`opt-in`|CA-QC region|
|
|
77
|
+
|`worldNoBanner()`|`none`|default fallback|
|
|
78
|
+
|
|
79
|
+
## Next.js Example (Hosted)
|
|
80
|
+
|
|
81
|
+
Point the provider at your backend — policy resolution happens server-side:
|
|
82
|
+
|
|
83
|
+
```tsx title="components/consent-manager/provider.tsx"
|
|
84
|
+
'use client';
|
|
85
|
+
|
|
86
|
+
import type { ReactNode } from 'react';
|
|
87
|
+
import {
|
|
88
|
+
ConsentBanner,
|
|
89
|
+
ConsentDialog,
|
|
90
|
+
ConsentManagerProvider,
|
|
91
|
+
} from '@c15t/nextjs';
|
|
92
|
+
|
|
93
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
94
|
+
return (
|
|
95
|
+
<ConsentManagerProvider
|
|
96
|
+
options={{
|
|
97
|
+
backendURL: 'https://your-instance.c15t.dev',
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<ConsentBanner />
|
|
101
|
+
<ConsentDialog />
|
|
102
|
+
{children}
|
|
103
|
+
</ConsentManagerProvider>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For production Next.js apps, you can optionally hydrate the first `/init` response with the [server-side utilities](/docs/frameworks/next/server-side).
|
|
109
|
+
|
|
110
|
+
## Offline / Fallback
|
|
111
|
+
|
|
112
|
+
For local development, previews, automated tests, or when the backend is temporarily unreachable, pass policies directly:
|
|
113
|
+
|
|
114
|
+
```tsx title="components/consent-manager/provider.tsx"
|
|
115
|
+
'use client';
|
|
116
|
+
|
|
117
|
+
import type { ReactNode } from 'react';
|
|
118
|
+
import {
|
|
119
|
+
ConsentBanner,
|
|
120
|
+
ConsentDialog,
|
|
121
|
+
ConsentManagerProvider,
|
|
122
|
+
policyPackPresets,
|
|
123
|
+
} from '@c15t/nextjs';
|
|
124
|
+
|
|
125
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
126
|
+
return (
|
|
127
|
+
<ConsentManagerProvider
|
|
128
|
+
options={{
|
|
129
|
+
mode: 'offline',
|
|
130
|
+
offlinePolicy: {
|
|
131
|
+
policyPacks: [
|
|
132
|
+
policyPackPresets.europeOptIn(),
|
|
133
|
+
policyPackPresets.californiaOptOut(),
|
|
134
|
+
policyPackPresets.worldNoBanner(),
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
overrides: {
|
|
138
|
+
country: 'DE', // Preview as a German visitor
|
|
139
|
+
},
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
<ConsentBanner />
|
|
143
|
+
<ConsentDialog />
|
|
144
|
+
{children}
|
|
145
|
+
</ConsentManagerProvider>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Provider Shape
|
|
151
|
+
|
|
152
|
+
Configure packs through `offlinePolicy.policyPacks`. Add `offlinePolicy.i18n`
|
|
153
|
+
when you want local previews or fallback behavior to mirror hosted policy-profile language behavior:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<ConsentManagerProvider
|
|
157
|
+
options={{
|
|
158
|
+
mode: 'offline',
|
|
159
|
+
offlinePolicy: {
|
|
160
|
+
i18n: {
|
|
161
|
+
defaultProfile: 'default',
|
|
162
|
+
messages: {
|
|
163
|
+
default: {
|
|
164
|
+
translations: {
|
|
165
|
+
en: { cookieBanner: { title: 'Privacy choices' } },
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
qc: {
|
|
169
|
+
fallbackLanguage: 'fr',
|
|
170
|
+
translations: {
|
|
171
|
+
en: { cookieBanner: { title: 'Quebec Privacy Settings' } },
|
|
172
|
+
fr: { cookieBanner: { title: 'Paramètres de confidentialité du Québec' } },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
policyPacks: [
|
|
178
|
+
{
|
|
179
|
+
id: 'qc_opt_in',
|
|
180
|
+
match: { regions: [{ country: 'CA', region: 'QC' }] },
|
|
181
|
+
i18n: { messageProfile: 'qc' },
|
|
182
|
+
consent: { model: 'opt-in', expiryDays: 365 },
|
|
183
|
+
ui: { mode: 'banner' },
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'default',
|
|
187
|
+
match: { isDefault: true },
|
|
188
|
+
consent: { model: 'none' },
|
|
189
|
+
ui: { mode: 'none' },
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
overrides: { country: 'CA', region: 'QC' },
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
With that setup, offline mode resolves language the same way as hosted mode:
|
|
199
|
+
|
|
200
|
+
* the active policy profile defines the allowed language set
|
|
201
|
+
* each profile can define its own `fallbackLanguage`
|
|
202
|
+
* built-in translations only fill missing keys for the selected language
|
|
203
|
+
|
|
204
|
+
## Fallback Behavior
|
|
205
|
+
|
|
206
|
+
|Configuration|Result|
|
|
207
|
+
|--|--|
|
|
208
|
+
|`offlinePolicy.policyPacks` omitted|Synthetic opt-in fallback banner (also used for hosted network fallback)|
|
|
209
|
+
|`offlinePolicy: { policyPacks: [] }`|Explicit no-banner mode|
|
|
210
|
+
|Non-empty pack, no match, no default|Explicit no-banner mode|
|
|
211
|
+
|
|
212
|
+
Omitting the option gives you a safe opt-in default for local development and outage scenarios. Providing it tells c15t you want deterministic preview or fallback behavior exactly as configured.
|
|
213
|
+
|
|
214
|
+
## QA and Debugging
|
|
215
|
+
|
|
216
|
+
The best way to test regional consent behavior is with the [c15t DevTools](/docs/frameworks/react/dev-tools). The DevTools Policy panel lets you simulate different countries, regions, and GPC signals against your real backend — no code changes needed.
|
|
217
|
+
|
|
218
|
+
For deeper inspection:
|
|
219
|
+
|
|
220
|
+
* Read `policy` and `policyDecision` from `useConsentManager()` to see the resolved config
|
|
221
|
+
* Open the DevTools Policy panel to inspect matcher resolution and fingerprints
|
|
222
|
+
* Compare your frontend preview with the backend `/init` response before shipping
|
|
223
|
+
|
|
224
|
+
If you need fully deterministic resolution without a backend during testing or preview work (for example, in automated tests or Storybook), pair `offlinePolicy.policyPacks` with `overrides`:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
options={{
|
|
228
|
+
mode: 'offline',
|
|
229
|
+
offlinePolicy: {
|
|
230
|
+
policyPacks: [
|
|
231
|
+
policyPackPresets.europeOptIn(),
|
|
232
|
+
policyPackPresets.californiaOptOut(),
|
|
233
|
+
policyPackPresets.worldNoBanner(),
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
overrides: {
|
|
237
|
+
country: 'US',
|
|
238
|
+
region: 'CA',
|
|
239
|
+
language: 'en-US',
|
|
240
|
+
gpc: true, // Simulate Global Privacy Control
|
|
241
|
+
},
|
|
242
|
+
}}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
> ℹ️ **Info:**
|
|
246
|
+
> For the full concept model (matchers, scope modes, re-prompting), see Policy Packs concepts. For backend setup, see the self-host guide.
|