@c15t/nextjs 2.0.0-rc.9 → 2.0.2

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.
@@ -2,8 +2,8 @@
2
2
  title: IABConsentDialog
3
3
  description: An IAB TCF 2.3 compliant preference center with tabbed purpose and vendor management.
4
4
  ---
5
- > **Error:**
6
- > c15t is not yet IAB certified. The IAB TCF components are under active development and should not be used in production. APIs and behavior may change before certification is achieved.
5
+ > ℹ️ **Info:**
6
+ > c15t's IAB TCF support can be used in production. Use Inth for a hosted, IAB TCF-certified CMP setup with a managed CMP ID, or register your own CMP with IAB Europe and configure your own CMP ID.
7
7
 
8
8
  `IABConsentDialog` is an IAB TCF 2.3 compliant consent dialog that provides a tabbed interface for managing purpose consent and vendor preferences. It includes purpose grouping via stacks, individual purpose/vendor toggles, special purpose and feature disclosures, and legitimate interest handling.
9
9
 
@@ -13,6 +13,7 @@ Pair it with `IABConsentBanner` inside the provider:
13
13
 
14
14
  ```tsx
15
15
  import { type ReactNode } from 'react';
16
+ import { iab } from '@c15t/iab';
16
17
  import { ConsentManagerProvider } from '@c15t/nextjs';
17
18
  import { IABConsentBanner, IABConsentDialog } from '@c15t/react/iab';
18
19
 
@@ -22,11 +23,12 @@ export default function ConsentManager({ children }: { children: ReactNode }) {
22
23
  options={{
23
24
  mode: 'hosted',
24
25
  backendURL: '/api/c15t',
25
- iab: {
26
- enabled: true,
27
- cmpId: 123,
26
+ iab: iab({
28
27
  vendors: [1, 2, 10, 25],
29
- },
28
+ // cmpId is automatically provided by the backend when using Inth.
29
+ // Only set this if you have your own CMP registration.
30
+ // cmpId: 123,
31
+ }),
30
32
  }}
31
33
  >
32
34
  <IABConsentBanner />
@@ -15,12 +15,12 @@ When your site participates in the IAB ecosystem (ad exchanges, SSPs, DSPs, DMPs
15
15
 
16
16
  ## CMP Registration
17
17
 
18
- [consent.io](https://consent.io) is pending validation as an IAB Europe-registered CMP for c15t. Once approved, when you use consent.io as your backend, the correct CMP ID will be automatically provided to your client via the `/init` endpoint — no client-side configuration needed.
18
+ [Inth](https://inth.com), c15t's hosted platform, is IAB TCF certified. When you use Inth as your backend with c15t's prebuilt IAB UI, the correct CMP ID is automatically provided to your client via the `/init` endpoint — no client-side configuration needed.
19
19
 
20
- If you self-host the c15t backend and have your own CMP registration with IAB Europe, you can configure your CMP ID on the backend via `advanced.iab.cmpId` or on the client via the `iab.cmpId` option. A valid (non-zero) CMP ID is required for IAB TCF compliance.
20
+ If you self-host the c15t backend or want to operate as your own CMP, register your own CMP with IAB Europe and configure your CMP ID on the backend via `advanced.iab.cmpId` or on the client via the `iab.cmpId` option. Registering your own CMP may also involve IAB Europe fees, so check IAB Europe's current CMP registration terms and pricing before choosing this route. A valid (non-zero) CMP ID is required for IAB TCF compliance.
21
21
 
22
22
  > ℹ️ **Info:**
23
- > If you heavily customize or build your own IAB banner or dialog (rather than using the default IABConsentBanner and IABConsentDialog components), you cannot use consent.io's CMP ID. You must register your own CMP with IAB Europe and use your own CMP ID.
23
+ > If you heavily customize or build your own IAB banner or dialog instead of using the default IABConsentBanner and IABConsentDialog components, you cannot use Inth's CMP ID. You must register your own CMP with IAB Europe and use your own CMP ID.
24
24
 
25
25
  ## How c15t Implements TCF
26
26
 
@@ -45,6 +45,7 @@ If you use the prebuilt styled IAB UI, import the IAB stylesheet alongside the b
45
45
 
46
46
  ```tsx
47
47
  import { type ReactNode } from 'react';
48
+ import { iab } from '@c15t/iab';
48
49
  import { ConsentManagerProvider } from '@c15t/nextjs';
49
50
  import { IABConsentBanner, IABConsentDialog } from '@c15t/react/iab';
50
51
 
@@ -54,13 +55,12 @@ export default function ConsentManager({ children }: { children: ReactNode }) {
54
55
  options={{
55
56
  mode: 'hosted',
56
57
  backendURL: '/api/c15t',
57
- iab: {
58
- enabled: true,
58
+ iab: iab({
59
59
  vendors: [1, 2, 10, 25], // IAB vendor IDs you work with
60
- // cmpId is automatically provided by the backend (consent.io).
60
+ // cmpId is automatically provided by the backend (inth.com).
61
61
  // Only set this if you have your own CMP registration with IAB Europe.
62
62
  // cmpId: 123,
63
- },
63
+ }),
64
64
  }}
65
65
  >
66
66
  <IABConsentBanner />
@@ -73,14 +73,13 @@ export default function ConsentManager({ children }: { children: ReactNode }) {
73
73
 
74
74
  ## IAB Configuration Options
75
75
 
76
- The `iab` option on the provider accepts:
76
+ Configure IAB mode with `iab({ ... })` from `@c15t/iab`. The factory enables the addon and injects the runtime module automatically. The user-facing options are:
77
77
 
78
78
  |Option|Type|Description|
79
79
  |--|--|--|
80
- |`enabled`|`boolean`|Enable IAB TCF mode|
81
- |`cmpId`|`number`|CMP ID registered with IAB Europe. Automatically provided by the backend when using consent.io. Only set this if you have your own CMP registration.|
80
+ |`cmpId`|`number`|CMP ID registered with IAB Europe. Automatically provided by the backend when using Inth with the prebuilt IAB UI. Only set this if you have your own CMP registration.|
82
81
  |`vendors`|`number[]`|IAB vendor IDs that your site works with|
83
- |`nonIABVendors`|`NonIABVendor[]`|Custom vendors not in the IAB registry|
82
+ |`customVendors`|`NonIABVendor[]`|Custom vendors not in the IAB registry|
84
83
 
85
84
  ## Key Concepts
86
85
 
@@ -122,4 +121,6 @@ Each vendor in the GVL declares which purposes it uses, whether via consent or l
122
121
  |--|--|
123
122
  |[IABConsentBanner](/docs/frameworks/next/iab/consent-banner)|TCF-compliant banner with partner disclosure|
124
123
  |[IABConsentDialog](/docs/frameworks/next/iab/consent-dialog)|Tabbed preference center for purposes and vendors|
125
- |[useGVLData](/docs/frameworks/next/iab/use-gvl-data)|Hook for building custom IAB UI|
124
+
125
+ > ℹ️ **Info:**
126
+ > For lower-level custom IAB flows, use useHeadlessIABConsentUI() from @c15t/react/iab. useGVLData() is currently internal and is not part of the public package surface.
@@ -1,208 +1,20 @@
1
1
  ---
2
- title: useGVLData
3
- description: Hook to access processed Global Vendor List (GVL) data for building custom IAB TCF UI components.
2
+ title: useGVLData (Internal)
3
+ description: Status note for the internal GVL hook used by the built-in IAB dialog.
4
4
  ---
5
- > ❌ **Error:**
6
- > c15t is not yet IAB certified. The IAB TCF components are under active development and should not be used in production. APIs and behavior may change before certification is achieved.
7
-
8
- `useGVLData()` processes the raw IAB Global Vendor List (GVL) into a UI-friendly format. It handles purpose grouping into stacks, vendor mapping, special purpose/feature extraction, and loading state.
9
-
10
- Use this hook when building a custom IAB TCF UI instead of the pre-built `IABConsentDialog`.
11
-
12
- ```tsx
13
- import { useGVLData } from '@c15t/react/hooks';
14
-
15
- function CustomIABPreferences() {
16
- const { purposes, stacks, standalonePurposes, totalVendors, isLoading } = useGVLData();
17
-
18
- if (isLoading) return <p>Loading vendor data...</p>;
19
-
20
- return (
21
- <div>
22
- <p>{totalVendors} partners</p>
23
- {standalonePurposes.map((purpose) => (
24
- <div key={purpose.id}>
25
- <h3>{purpose.name}</h3>
26
- <p>{purpose.description}</p>
27
- <p>{purpose.vendors.length} vendors</p>
28
- </div>
29
- ))}
30
- </div>
31
- );
32
- }
33
- ```
34
-
35
5
  > ℹ️ **Info:**
36
- > Must be used within a ConsentManagerProvider with IAB mode enabled. Returns empty data if IAB is not configured.
37
-
38
- ## Return Value
39
-
40
- The hook returns a `GVLData` object:
41
-
42
- ### GVLData
43
-
44
- |Property|Type|Description|Default|Required|
45
- |:--|:--|:--|:--|:--:|
46
- |purposes|ProcessedPurpose|-|-|✅ Required|
47
- |specialPurposes|ProcessedPurpose|-|-|✅ Required|
48
- |specialFeatures|ProcessedSpecialFeature|-|-|✅ Required|
49
- |features|ProcessedFeature|-|-|✅ Required|
50
- |stacks|ProcessedStack|-|-|✅ Required|
51
- |standalonePurposes|ProcessedPurpose|-|-|✅ Required|
52
- |totalVendors|number|-|-|✅ Required|
53
- |isLoading|boolean|-|-|✅ Required|
54
-
55
- #### `purposes` ProcessedPurpose
56
-
57
- |Property|Type|Description|Default|Required|
58
- |:--|:--|:--|:--|:--:|
59
- |id|number|-|-|✅ Required|
60
- |name|string|-|-|✅ Required|
61
- |description|string|-|-|✅ Required|
62
- |descriptionLegal|string \|undefined|-|-|Optional|
63
- |illustrations|string\[]|-|-|✅ Required|
64
- |vendors|ProcessedVendor|-|-|✅ Required|
65
- |isSpecialPurpose|boolean \|undefined|-|-|Optional|
66
-
67
- #### `specialPurposes` ProcessedPurpose
68
-
69
- |Property|Type|Description|Default|Required|
70
- |:--|:--|:--|:--|:--:|
71
- |id|number|-|-|✅ Required|
72
- |name|string|-|-|✅ Required|
73
- |description|string|-|-|✅ Required|
74
- |descriptionLegal|string \|undefined|-|-|Optional|
75
- |illustrations|string\[]|-|-|✅ Required|
76
- |vendors|ProcessedVendor|-|-|✅ Required|
77
- |isSpecialPurpose|boolean \|undefined|-|-|Optional|
78
-
79
- #### `specialFeatures` ProcessedSpecialFeature
6
+ > c15t's IAB TCF support can be used in production through Inth or with your own registered CMP ID. useGVLData() is different: it remains an internal hook and is not part of the supported public API.
80
7
 
81
- |Property|Type|Description|Default|Required|
82
- |:--|:--|:--|:--|:--:|
83
- |id|number|-|-|✅ Required|
84
- |name|string|-|-|✅ Required|
85
- |description|string|-|-|✅ Required|
86
- |descriptionLegal|string \|undefined|-|-|Optional|
87
- |illustrations|string\[]|-|-|✅ Required|
88
- |vendors|ProcessedVendor|-|-|✅ Required|
8
+ `useGVLData()` currently powers the built-in `IABConsentDialog`, but it is **not part of the public package surface**.
89
9
 
90
- #### `features` ProcessedFeature
10
+ Older docs showed it as a public hook. That is no longer accurate.
91
11
 
92
- |Property|Type|Description|Default|Required|
93
- |:--|:--|:--|:--|:--:|
94
- |id|number|-|-|✅ Required|
95
- |name|string|-|-|✅ Required|
96
- |description|string|-|-|✅ Required|
97
- |descriptionLegal|string \|undefined|-|-|Optional|
98
- |illustrations|string\[]|-|-|✅ Required|
99
- |vendors|ProcessedVendor|-|-|✅ Required|
12
+ If you need supported customization points today:
100
13
 
101
- #### `stacks` ProcessedStack
14
+ * Use `IABConsentBanner` and `IABConsentDialog` from `@c15t/react/iab` for the supported prebuilt UI
15
+ * Use `useHeadlessIABConsentUI()` from `@c15t/react/iab` when you need lower-level control over banner/dialog state and actions
102
16
 
103
- |Property|Type|Description|Default|Required|
104
- |:--|:--|:--|:--|:--:|
105
- |id|number|-|-|✅ Required|
106
- |name|string|-|-|✅ Required|
107
- |description|string|-|-|✅ Required|
108
- |purposes|ProcessedPurpose|-|-|✅ Required|
109
-
110
- #### `standalonePurposes` ProcessedPurpose
111
-
112
- |Property|Type|Description|Default|Required|
113
- |:--|:--|:--|:--|:--:|
114
- |id|number|-|-|✅ Required|
115
- |name|string|-|-|✅ Required|
116
- |description|string|-|-|✅ Required|
117
- |descriptionLegal|string \|undefined|-|-|Optional|
118
- |illustrations|string\[]|-|-|✅ Required|
119
- |vendors|ProcessedVendor|-|-|✅ Required|
120
- |isSpecialPurpose|boolean \|undefined|-|-|Optional|
121
-
122
- ## Types
123
-
124
- ### ProcessedPurpose
125
-
126
- |Property|Type|Description|Default|Required|
127
- |:--|:--|:--|:--|:--:|
128
- |id|number|-|-|✅ Required|
129
- |name|string|-|-|✅ Required|
130
- |description|string|-|-|✅ Required|
131
- |descriptionLegal|string \|undefined|-|-|Optional|
132
- |illustrations|string\[]|-|-|✅ Required|
133
- |vendors|ProcessedVendor|-|-|✅ Required|
134
- |isSpecialPurpose|boolean \|undefined|-|-|Optional|
135
-
136
- #### `vendors` ProcessedVendor
137
-
138
- |Property|Type|Description|Default|Required|
139
- |:--|:--|:--|:--|:--:|
140
- |id|VendorId|-|-|✅ Required|
141
- |name|string|-|-|✅ Required|
142
- |policyUrl|string|-|-|✅ Required|
143
- |usesNonCookieAccess|boolean|-|-|✅ Required|
144
- |deviceStorageDisclosureUrl|string \|null|-|-|✅ Required|
145
- |usesCookies|boolean|-|-|✅ Required|
146
- |cookieMaxAgeSeconds|number \|null|-|-|✅ Required|
147
- |cookieRefresh|boolean \|undefined|-|-|Optional|
148
- |specialPurposes|number\[]|-|-|✅ Required|
149
- |specialFeatures|number\[]|-|-|✅ Required|
150
- |features|number\[]|-|-|✅ Required|
151
- |purposes|number\[]|-|-|✅ Required|
152
- |legIntPurposes|number\[]|-|-|✅ Required|
153
- |legitimateInterestUrl|string \|null \|undefined|-|-|Optional|
154
- |isCustom|boolean \|undefined|-|-|Optional|
155
- |usesLegitimateInterest|boolean \|undefined|-|-|Optional|
156
- |dataRetention|Object \|undefined|-|-|Optional|
157
- |dataDeclaration|number\[] \|undefined|-|-|Optional|
158
-
159
- ### ProcessedVendor
160
-
161
- |Property|Type|Description|Default|Required|
162
- |:--|:--|:--|:--|:--:|
163
- |id|VendorId|-|-|✅ Required|
164
- |name|string|-|-|✅ Required|
165
- |policyUrl|string|-|-|✅ Required|
166
- |usesNonCookieAccess|boolean|-|-|✅ Required|
167
- |deviceStorageDisclosureUrl|string \|null|-|-|✅ Required|
168
- |usesCookies|boolean|-|-|✅ Required|
169
- |cookieMaxAgeSeconds|number \|null|-|-|✅ Required|
170
- |cookieRefresh|boolean \|undefined|-|-|Optional|
171
- |specialPurposes|number\[]|-|-|✅ Required|
172
- |specialFeatures|number\[]|-|-|✅ Required|
173
- |features|number\[]|-|-|✅ Required|
174
- |purposes|number\[]|-|-|✅ Required|
175
- |legIntPurposes|number\[]|-|-|✅ Required|
176
- |legitimateInterestUrl|string \|null \|undefined|-|-|Optional|
177
- |isCustom|boolean \|undefined|-|-|Optional|
178
- |usesLegitimateInterest|boolean \|undefined|-|-|Optional|
179
- |dataRetention|Object \|undefined|-|-|Optional|
180
- |dataDeclaration|number\[] \|undefined|-|-|Optional|
181
-
182
- #### `dataRetention`
183
-
184
- |Property|Type|Description|Default|Required|
185
- |:--|:--|:--|:--|:--:|
186
- |purposes|Record\<number, number> \|undefined|-|-|Optional|
187
- |specialPurposes|Record\<number, number> \|undefined|-|-|Optional|
188
- |stdRetention|number \|undefined|-|-|Optional|
189
-
190
- ### ProcessedStack
191
-
192
- |Property|Type|Description|Default|Required|
193
- |:--|:--|:--|:--|:--:|
194
- |id|number|-|-|✅ Required|
195
- |name|string|-|-|✅ Required|
196
- |description|string|-|-|✅ Required|
197
- |purposes|ProcessedPurpose|-|-|✅ Required|
198
-
199
- ### ProcessedSpecialFeature
17
+ > ℹ️ **Info:**
18
+ > Until useGVLData() is exported as public API, avoid importing it from deep internal paths. Those paths are not covered by semver guarantees and can change without notice.
200
19
 
201
- |Property|Type|Description|Default|Required|
202
- |:--|:--|:--|:--|:--:|
203
- |id|number|-|-|✅ Required|
204
- |name|string|-|-|✅ Required|
205
- |description|string|-|-|✅ Required|
206
- |descriptionLegal|string \|undefined|-|-|Optional|
207
- |illustrations|string\[]|-|-|✅ Required|
208
- |vendors|ProcessedVendor|-|-|✅ Required|
20
+ When a public GVL-focused hook becomes part of the supported API, this page should document that public surface instead of the internal dialog hook.
@@ -10,7 +10,7 @@ There are two ways c15t can load translations: client-side or server-side.
10
10
 
11
11
  |Server-side|Client-side|
12
12
  |--|--|
13
- |The best way to reduce bundle size and improve performance. We can detect the user's language based on the browser's language settings, allowing for the most accurate translations. By default, when using a [consent.io](https://consent.io) hosted instance, [these languages](https://github.com/c15t/c15t/tree/main/packages/translations/src/translations) are supported.|Bundled with the application allowing for multiple languages to be supported without the need for a backend. The more translations you have, the larger the bundle size will be, which may impact the performance of your application.|
13
+ |The best way to reduce bundle size and improve performance. We can detect the user's language based on the browser's language settings, allowing for the most accurate translations. By default, when using a [inth.com](https://inth.com) hosted instance, [these languages](https://github.com/c15t/c15t/tree/main/packages/translations/src/translations) are supported.|Bundled with the application allowing for multiple languages to be supported without the need for a backend. The more translations you have, the larger the bundle size will be, which may impact the performance of your application.|
14
14
 
15
15
  ## Basic Configuration
16
16
 
@@ -1,10 +1,35 @@
1
1
  ---
2
2
  title: Optimization
3
- description: Improve c15t startup performance in Next.js with rewrites, static prefetching, and rendering tradeoffs.
4
- lastModified: 2026-04-09
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
5
  ---
6
6
  Use this guide when you care about banner visibility speed, route static-ness, and reducing backend round-trip cost.
7
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
+
8
33
  ## 1) Prefer Same-Origin Rewrites
9
34
 
10
35
  Proxy c15t requests through your Next.js app so the browser calls your own origin instead of a third-party domain.
@@ -39,30 +64,24 @@ Why this helps:
39
64
  * You can change backend infrastructure without touching client code
40
65
 
41
66
  > ℹ️ **Info:**
42
- > Set NEXT\_PUBLIC\_C15T\_URL in .env to your backend URL, for example https\://your-instance.c15t.dev.
67
+ > Set NEXT\_PUBLIC\_C15T\_URL in .env to your backend URL, for example https\://your-project.inth.app.
43
68
  >
44
69
  > ℹ️ **Info:**
45
- > Use rewrites for browser-side calls (ConsentManagerProvider, C15tPrefetch). For server-side fetchInitialData(), prefer a direct backend URL (for example https\://your-instance.c15t.dev) to avoid an extra server proxy hop.
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.
46
71
 
47
- ## 2) Pick The Right Data Strategy
72
+ ## 2) Choose A Startup Strategy
48
73
 
49
- |Goal|Strategy|Tradeoff|
50
- |--|--|--|
51
- |Keep routes fully static|`C15tPrefetch`|Still client-side fetch, but starts earlier|
52
- |Fastest first banner on dynamic routes|`fetchInitialData()` in a Server Component (do not await)|Route becomes dynamic because of `next/headers`|
53
- |Simplest setup|Client-only init (no prefetch)|Banner appears later on slow networks|
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.
54
75
 
55
- In production benchmarks with a same-origin rewrite, prefetching strategies show measurable improvement over client-only init:
76
+ ### Client-Only Init
56
77
 
57
- |Strategy|Scripts loaded|Data request starts|Banner visible|
58
- |--|--|--|--|
59
- |Client-only (no prefetch)|baseline|baseline|baseline|
60
- |Browser prefetch|\~1.3x faster|\~2.6x earlier|\~1.25x faster|
61
- |Server prefetch|\~2x faster|before page loads|\~1.9x faster|
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.
62
79
 
63
80
  ### Dynamic Routes: Fetch On The Server And Stream
64
81
 
65
- Use `fetchInitialData()` in a Server Component when you want the fastest first banner and can accept the route becoming dynamic.
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.
66
85
 
67
86
  ```tsx title="app/layout.tsx"
68
87
  import { fetchInitialData } from '@c15t/nextjs';
@@ -106,7 +125,7 @@ export default function ConsentManager({
106
125
  options={{
107
126
  mode: 'hosted',
108
127
  backendURL: '/api/c15t',
109
- store: { ssrData },
128
+ ssrData,
110
129
  }}
111
130
  >
112
131
  <ConsentBanner />
@@ -121,11 +140,11 @@ export default function ConsentManager({
121
140
  > Do not await fetchInitialData(). Pass the unresolved Promise to the provider so Next.js can stream the route while /init runs in parallel.
122
141
  >
123
142
  > ℹ️ **Info:**
124
- > For fetchInitialData(), prefer a direct backend URL such as https\://your-instance.c15t.dev instead of a rewrite to avoid an extra server-side proxy hop. See Server-Side Data Fetching for the full flow.
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.
125
144
 
126
145
  ### Static Routes: Start Fetch Early In The Browser
127
146
 
128
- Use `C15tPrefetch` in your layout. Matching prefetched data is consumed automatically by the runtime during first store initialization.
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.
129
148
 
130
149
  ```tsx title="app/layout.tsx"
131
150
  import { C15tPrefetch } from '@c15t/nextjs';
@@ -175,10 +194,10 @@ export default function ConsentManagerClient({ children }: { children: React.Rea
175
194
  ```
176
195
 
177
196
  > ℹ️ **Info:**
178
- > C15tPrefetch uses Next.js beforeInteractive script loading, so the /init request can start before hydration. In App Router streaming output, this may appear as Next.js script bootstrap data (for example self.\_\_next\_s.push(...)) instead of a literal \<head>\<script> tag.
197
+ > C15tPrefetch uses Next.js beforeInteractive script loading, so the /init request can start before hydration.
179
198
  >
180
199
  > ℹ️ **Info:**
181
- > If overrides.gpc conflicts with the browser's ambient GPC signal, the prefetched entry is not reused and c15t falls back to a normal client /init.
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.
182
201
 
183
202
  ## Keep The Provider Mounted Across Navigation
184
203
 
@@ -210,6 +229,6 @@ If you must use a cross-origin backend URL, add preconnect so the browser starts
210
229
 
211
230
  ```tsx title="app/layout.tsx"
212
231
  <head>
213
- <link rel="preconnect" href="https://your-instance.c15t.dev" crossOrigin="" />
232
+ <link rel="preconnect" href="https://your-project.inth.app" crossOrigin="" />
214
233
  </head>
215
234
  ```
@@ -13,7 +13,7 @@ When a backend isn't available — local development, static previews, Storybook
13
13
 
14
14
  ## Hosted Mode (Recommended)
15
15
 
16
- When using consent.io or a self-hosted backend, the provider connects automatically. No policy configuration is needed on the frontend:
16
+ When using inth.com or a self-hosted backend, the provider connects automatically. No policy configuration is needed on the frontend:
17
17
 
18
18
  ```tsx
19
19
  <ConsentManagerProvider
@@ -17,10 +17,10 @@ availableIn:
17
17
 
18
18
  |Package manager|Command|
19
19
  |:--|:--|
20
- |npm|`npx @c15t/cli@rc`|
21
- |pnpm|`pnpm dlx @c15t/cli@rc`|
22
- |yarn|`yarn dlx @c15t/cli@rc`|
23
- |bun|`bunx @c15t/cli@rc`|
20
+ |npm|`npx @c15t/cli`|
21
+ |pnpm|`pnpm dlx @c15t/cli`|
22
+ |yarn|`yarn dlx @c15t/cli`|
23
+ |bun|`bunx @c15t/cli`|
24
24
 
25
25
  ## Manual Installation
26
26
 
@@ -39,7 +39,7 @@ availableIn:
39
39
  @import "@c15t/nextjs/styles.css";
40
40
  ```
41
41
 
42
- Keeping the c15t stylesheet in your global CSS entrypoint makes layer and cascade order explicit. JS/TSX side-effect imports can load in a different order across framework and Tailwind tooling, which makes style regressions harder to debug.
42
+ Keeping the c15t stylesheet in your global CSS entrypoint makes layer and cascade order explicit. JS/TSX side-effect imports can load in a different order across framework and Tailwind tooling, which makes style regressions harder to debug.With Tailwind v4, keep c15t at the end of the top-level @import block so Fumadocs, tw-animate-css, and other preset imports do not override c15t theme tokens.
43
43
 
44
44
  > ℹ️ Info:
45
45
  >
@@ -151,10 +151,10 @@ Install c15t agent skills to let AI agents help with styling, i18n, scripts & ot
151
151
 
152
152
  |Package manager|Command|
153
153
  |:--|:--|
154
- |npm|`npx @c15t/cli@rc skills`|
155
- |pnpm|`pnpm dlx @c15t/cli@rc skills`|
156
- |yarn|`yarn dlx @c15t/cli@rc skills`|
157
- |bun|`bunx @c15t/cli@rc skills`|
154
+ |npm|`npx @c15t/cli skills`|
155
+ |pnpm|`pnpm dlx @c15t/cli skills`|
156
+ |yarn|`yarn dlx @c15t/cli skills`|
157
+ |bun|`bunx @c15t/cli skills`|
158
158
 
159
159
  See [AI Agents](/docs/ai-agents) for bundled package docs and agent skills.
160
160
 
@@ -1,8 +1,10 @@
1
1
  ---
2
2
  title: Server-Side Data Fetching
3
- description: Pre-fetch consent data in Server Components with fetchInitialData eliminate the loading flash and enable streaming.
3
+ description: Pre-fetch consent data in Server Components with fetchInitialData for dynamic Next.js routes.
4
4
  ---
5
- The `@c15t/nextjs` package provides `fetchInitialData`, a server-side function that fetches consent data before rendering. This enables SSR hydration consent state is available immediately on page load without a client-side fetch, eliminating the consent banner flash.
5
+ The `@c15t/nextjs` package provides `fetchInitialData`, a server-side function that fetches consent data before rendering. Use it when the route is already dynamic, or when you want the fastest first banner and are okay opting the route into dynamic rendering. If you need to keep a route fully static, use `C15tPrefetch` instead.
6
+
7
+ Because `fetchInitialData()` resolves request headers from `next/headers`, using it opts the route into dynamic rendering.
6
8
 
7
9
  > ℹ️ **Info:**
8
10
  > SSR hydration is part of the initialization flow. When SSR data is available, the client skips the API fetch entirely.
@@ -20,7 +22,7 @@ import { fetchInitialData } from '@c15t/nextjs';
20
22
 
21
23
  // In a Server Component — do NOT await
22
24
  const ssrData = fetchInitialData({
23
- backendURL: '/api/c15t',
25
+ backendURL: 'https://your-instance.c15t.dev',
24
26
  debug: process.env.NODE_ENV === 'development',
25
27
  });
26
28
  ```
@@ -29,6 +31,9 @@ const ssrData = fetchInitialData({
29
31
  > Do not await fetchInitialData() in Server Components. Pass the Promise directly to your client component so Next.js can stream the page while the consent data loads in parallel.
30
32
  >
31
33
  > ℹ️ **Info:**
34
+ > For fetchInitialData(), prefer a direct backend URL such as https\://your-instance.c15t.dev. For browser-side calls from the provider, C15tPrefetch, and other client runtime work, prefer a same-origin /api/c15t rewrite. See Optimization for the full decision guide.
35
+ >
36
+ > ℹ️ **Info:**
32
37
  > Need fully static routes? Use C15tPrefetch in your layout. Matching prefetched data is consumed automatically by the runtime instead of using fetchInitialData(). See Optimization.
33
38
 
34
39
  ### How Streaming Works
@@ -94,7 +99,7 @@ export default function ConsentManager({
94
99
  options={{
95
100
  mode: 'hosted',
96
101
  backendURL: '/api/c15t',
97
- store: { ssrData },
102
+ ssrData,
98
103
  }}
99
104
  >
100
105
  <ConsentBanner />
@@ -111,7 +116,7 @@ import ConsentManager from '@/components/consent-manager';
111
116
 
112
117
  export default function RootLayout({ children }: { children: React.ReactNode }) {
113
118
  const ssrData = fetchInitialData({
114
- backendURL: '/api/c15t',
119
+ backendURL: 'https://your-instance.c15t.dev',
115
120
  });
116
121
 
117
122
  return (
@@ -65,7 +65,7 @@ function ThemeToggle() {
65
65
 
66
66
  ## How Dark Mode Works
67
67
 
68
- When dark mode is active, c15t applies the `dark` token values as CSS variable overrides. Only tokens specified in `dark` are overridden - unset tokens fall back to the `colors` values.
68
+ When dark mode is active, c15t applies the `dark` token values as CSS variable overrides. Only tokens specified in `dark` are overridden - unset tokens fall back to the `colors` values. This also applies to `textOnPrimary`: if you omit it, c15t derives a readable foreground from the active `primary` color in that scheme.
69
69
 
70
70
  ```tsx
71
71
  const theme = {
@@ -18,7 +18,7 @@ Every theme token is converted to a `--c15t-*` CSS custom property at runtime. Y
18
18
  |--c15t-border-hover|string \|undefined|\`colors.borderHover\` (default: \`hsl(0, 0%, 85%)\`)|-|Optional|
19
19
  |--c15t-text|string \|undefined|\`colors.text\` (default: \`hsl(0, 0%, 10%)\`)|-|Optional|
20
20
  |--c15t-text-muted|string \|undefined|\`colors.textMuted\` (default: \`hsl(0, 0%, 40%)\`)|-|Optional|
21
- |--c15t-text-on-primary|string \|undefined|\`colors.textOnPrimary\` (default: \`hsl(0, 0%, 100%)\`)|-|Optional|
21
+ |--c15t-text-on-primary|string \|undefined|\`colors.textOnPrimary\` (auto-derived from \`colors.primary\` when omitted)|-|Optional|
22
22
  |--c15t-overlay|string \|undefined|\`colors.overlay\` (default: \`hsla(0, 0%, 0%, 0.5)\`)|-|Optional|
23
23
  |--c15t-switch-track|string \|undefined|\`colors.switchTrack\` (default: \`hsl(0, 0%, 85%)\`)|-|Optional|
24
24
  |--c15t-switch-track-active|string \|undefined|\`colors.switchTrackActive\` (default: \`hsl(228, 100%, 60%)\`)|-|Optional|
@@ -99,6 +99,7 @@ Use tokens first when the change is semantic:
99
99
  * Banner card background -> `theme.colors.surface`
100
100
  * Banner footer background -> `theme.colors.surfaceHover`
101
101
  * Shared copy color -> `theme.colors.text` and `theme.colors.textMuted`
102
+ * Primary-filled surfaces such as stock branding tags and filled actions -> `theme.colors.primary` with `theme.colors.textOnPrimary` as the matching foreground override
102
103
 
103
104
  ```tsx
104
105
  options={{
@@ -111,6 +112,8 @@ options={{
111
112
  }}
112
113
  ```
113
114
 
115
+ If you set `theme.colors.primary` but omit `theme.colors.textOnPrimary`, c15t derives a readable foreground automatically. Add `textOnPrimary` only when you need to force a specific branded foreground color.
116
+
114
117
  ### 3. Component slots
115
118
 
116
119
  Target specific component parts via the `slots` object:
@@ -314,7 +317,7 @@ Color palette for light mode.
314
317
  |borderHover|string \|undefined|Hover state for bordered elements.|-|Optional|
315
318
  |text|string \|undefined|Primary text color for headings and body.|-|Optional|
316
319
  |textMuted|string \|undefined|Muted text color for secondary content.|-|Optional|
317
- |textOnPrimary|string \|undefined|Text color for content on primary background.|-|Optional|
320
+ |textOnPrimary|string \|undefined|Text color for content on primary background. Auto-derived from \`primary\` when omitted.|-|Optional|
318
321
  |overlay|string \|undefined|Overlay color for modal backdrops.|-|Optional|
319
322
  |switchTrack|string \|undefined|Toggle track color (off state).|-|Optional|
320
323
  |switchTrackActive|string \|undefined|Toggle track color (on state).|-|Optional|
@@ -334,7 +337,7 @@ Dark mode color overrides.
334
337
  |borderHover|string \|undefined|Hover state for bordered elements.|-|Optional|
335
338
  |text|string \|undefined|Primary text color for headings and body.|-|Optional|
336
339
  |textMuted|string \|undefined|Muted text color for secondary content.|-|Optional|
337
- |textOnPrimary|string \|undefined|Text color for content on primary background.|-|Optional|
340
+ |textOnPrimary|string \|undefined|Text color for content on primary background. Auto-derived from \`primary\` when omitted.|-|Optional|
338
341
  |overlay|string \|undefined|Overlay color for modal backdrops.|-|Optional|
339
342
  |switchTrack|string \|undefined|Toggle track color (off state).|-|Optional|
340
343
  |switchTrackActive|string \|undefined|Toggle track color (on state).|-|Optional|
@@ -420,6 +423,7 @@ Component-specific style overrides.
420
423
  |consentBannerDescription|SlotStyle \|undefined|Banner description text element.|-|Optional|
421
424
  |consentBannerFooter|SlotStyle \|undefined|Footer container for banner action buttons.|-|Optional|
422
425
  |consentBannerFooterSubGroup|SlotStyle \|undefined|Nested button group inside the banner footer.|-|Optional|
426
+ |consentBannerTag|SlotStyle \|undefined|Branding tag rendered above the consent banner card.|-|Optional|
423
427
  |consentBannerOverlay|SlotStyle \|undefined|Backdrop overlay rendered behind the banner when enabled.|-|Optional|
424
428
  |consentDialog|SlotStyle \|undefined|Root wrapper for the consent dialog modal.|-|Optional|
425
429
  |consentDialogCard|SlotStyle \|undefined|Main dialog card container.|-|Optional|
@@ -427,22 +431,25 @@ Component-specific style overrides.
427
431
  |consentDialogTitle|SlotStyle \|undefined|Dialog title text element.|-|Optional|
428
432
  |consentDialogDescription|SlotStyle \|undefined|Dialog description text element.|-|Optional|
429
433
  |consentDialogContent|SlotStyle \|undefined|Dialog content region (typically holds ConsentWidget).|-|Optional|
430
- |consentDialogFooter|SlotStyle \|undefined|Dialog footer container with actions/branding.|-|Optional|
434
+ |consentDialogFooter|SlotStyle \|undefined|Footer container used by compound dialog layouts.|-|Optional|
435
+ |consentDialogTag|SlotStyle \|undefined|Branding tag rendered below the stock consent dialog card.|-|Optional|
431
436
  |consentDialogOverlay|SlotStyle \|undefined|Backdrop overlay rendered behind the dialog.|-|Optional|
432
437
  |consentWidget|SlotStyle \|undefined|Root wrapper for the consent widget/preferences panel.|-|Optional|
433
438
  |consentWidgetAccordion|SlotStyle \|undefined|Accordion region listing consent categories.|-|Optional|
434
439
  |consentWidgetFooter|SlotStyle \|undefined|Footer area for widget actions and links.|-|Optional|
435
- |consentWidgetBranding|SlotStyle \|undefined|Branding element rendered in the widget footer.|-|Optional|
440
+ |consentWidgetTag|SlotStyle \|undefined|Branding tag rendered below the standalone consent widget.|-|Optional|
436
441
  |frame|SlotStyle \|undefined|Frame wrapper used by blocking placeholders (e.g., iframe blocking).|-|Optional|
437
442
  |iabConsentBanner|SlotStyle \|undefined|Root wrapper for the IAB consent banner.|-|Optional|
438
443
  |iabConsentBannerCard|SlotStyle \|undefined|Main card container for IAB banner content.|-|Optional|
439
444
  |iabConsentBannerHeader|SlotStyle \|undefined|Header region for IAB banner title/description.|-|Optional|
440
445
  |iabConsentBannerFooter|SlotStyle \|undefined|Footer container for IAB banner actions.|-|Optional|
446
+ |iabConsentBannerTag|SlotStyle \|undefined|Branding tag rendered above the IAB banner card.|-|Optional|
441
447
  |iabConsentBannerOverlay|SlotStyle \|undefined|Backdrop overlay rendered behind the IAB banner.|-|Optional|
442
448
  |iabConsentDialog|SlotStyle \|undefined|Root wrapper for the IAB consent dialog.|-|Optional|
443
449
  |iabConsentDialogCard|SlotStyle \|undefined|Main card container for IAB dialog content.|-|Optional|
444
450
  |iabConsentDialogHeader|SlotStyle \|undefined|Header region for IAB dialog title/description.|-|Optional|
445
451
  |iabConsentDialogFooter|SlotStyle \|undefined|Footer container for IAB dialog actions.|-|Optional|
452
+ |iabConsentDialogTag|SlotStyle \|undefined|Branding tag rendered below the IAB dialog card.|-|Optional|
446
453
  |iabConsentDialogOverlay|SlotStyle \|undefined|Backdrop overlay rendered behind the IAB dialog.|-|Optional|
447
454
  |buttonPrimary|SlotStyle \|undefined|Shared primary button style used across consent components.|-|Optional|
448
455
  |buttonSecondary|SlotStyle \|undefined|Shared secondary button style used across consent components.|-|Optional|