@c15t/nextjs 2.2.0 → 3.0.0-alpha.0

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 (220) hide show
  1. package/AGENTS.md +102 -142
  2. package/README.md +4 -4
  3. package/dist/api.js +1 -0
  4. package/dist/config.js +1 -0
  5. package/dist/devtools.js +2 -0
  6. package/dist/headers.js +1 -0
  7. package/dist/iab/styles.css +1 -1
  8. package/dist/iab/styles.tw3.css +133 -19
  9. package/dist/index.js +1 -1
  10. package/dist/middleware.js +1 -0
  11. package/dist/node-bridge.js +1 -0
  12. package/dist/pages.js +1 -0
  13. package/dist/proxy.js +1 -0
  14. package/dist/root.js +2 -0
  15. package/dist/server.js +1 -0
  16. package/dist/static.js +1 -0
  17. package/dist/styles.css +1 -1
  18. package/dist/styles.tw3.css +173 -29
  19. package/dist/version.js +1 -1
  20. package/dist-types/api.d.ts +88 -0
  21. package/dist-types/config.d.ts +110 -0
  22. package/dist-types/devtools.d.ts +1 -0
  23. package/dist-types/headers.d.ts +4 -0
  24. package/dist-types/index.d.ts +28 -10
  25. package/dist-types/middleware.d.ts +15 -0
  26. package/dist-types/node-bridge.d.ts +57 -0
  27. package/dist-types/pages.d.ts +88 -0
  28. package/dist-types/proxy.d.ts +34 -0
  29. package/dist-types/root.d.ts +86 -0
  30. package/dist-types/server.d.ts +164 -0
  31. package/dist-types/static.d.ts +42 -0
  32. package/dist-types/types.d.ts +5 -36
  33. package/dist-types/version.d.ts +1 -1
  34. package/docs/README.md +102 -142
  35. package/docs/assets/v3/brand-bar.png +0 -0
  36. package/docs/assets/v3/brand-card.png +0 -0
  37. package/docs/assets/v3/choice-wall.png +0 -0
  38. package/docs/assets/v3/mobile-card.png +0 -0
  39. package/docs/assets/v3/preferences.png +0 -0
  40. package/docs/customization/overview.md +45 -0
  41. package/docs/customization/recipes.md +79 -0
  42. package/docs/customization/slots.md +55 -0
  43. package/docs/customization/tokens.md +76 -0
  44. package/docs/customization/translations.md +49 -0
  45. package/docs/frameworks/next/api-reference/data-fetching.md +416 -0
  46. package/docs/frameworks/next/app-router.md +403 -0
  47. package/docs/frameworks/next/client-side.md +118 -0
  48. package/docs/frameworks/next/components/consent-banner.md +251 -211
  49. package/docs/frameworks/next/components/consent-dialog-link.md +96 -35
  50. package/docs/frameworks/next/components/consent-dialog-trigger.md +74 -149
  51. package/docs/frameworks/next/components/consent-dialog.md +189 -134
  52. package/docs/frameworks/next/components/consent-manager-provider.md +60 -318
  53. package/docs/frameworks/next/components/consent-widget.md +172 -114
  54. package/docs/frameworks/next/components/dev-tools.md +199 -40
  55. package/docs/frameworks/next/components/frame.md +137 -42
  56. package/docs/frameworks/next/concepts/consent-categories.md +24 -89
  57. package/docs/frameworks/next/concepts/policy-presets.md +142 -0
  58. package/docs/frameworks/next/content-security-policy.md +189 -0
  59. package/docs/frameworks/next/data-fetching.md +74 -0
  60. package/docs/frameworks/next/geography-headers.md +251 -0
  61. package/docs/frameworks/next/headless.md +95 -185
  62. package/docs/frameworks/next/hooks/use-consent-manager/overview.md +42 -163
  63. package/docs/frameworks/next/iab/overview.md +37 -107
  64. package/docs/frameworks/next/optimization.md +158 -194
  65. package/docs/frameworks/next/pages-router.md +296 -0
  66. package/docs/frameworks/next/quickstart.md +31 -132
  67. package/docs/frameworks/next/script-loader.md +134 -467
  68. package/docs/frameworks/next/server-side.md +97 -130
  69. package/docs/frameworks/next/static-export.md +164 -0
  70. package/docs/frameworks/next/styling/overview.md +166 -250
  71. package/docs/frameworks/next/troubleshooting.md +134 -144
  72. package/docs/guides/consent-state.md +60 -0
  73. package/docs/guides/data-fetching.md +163 -0
  74. package/docs/guides/deployment-modes.md +63 -0
  75. package/docs/guides/troubleshooting.md +68 -0
  76. package/docs/guides/verify-consent.md +62 -0
  77. package/docs/integrations/adobe-analytics.md +239 -105
  78. package/docs/integrations/ahrefs-analytics.md +238 -104
  79. package/docs/integrations/amplitude.md +219 -157
  80. package/docs/integrations/building-integrations.md +32 -224
  81. package/docs/integrations/clearbit.md +247 -86
  82. package/docs/integrations/cloudflare-web-analytics.md +250 -84
  83. package/docs/integrations/crisp.md +251 -97
  84. package/docs/integrations/databuddy.md +259 -153
  85. package/docs/integrations/fathom-analytics.md +239 -96
  86. package/docs/integrations/google-maps.md +328 -207
  87. package/docs/integrations/google-tag-manager.md +248 -96
  88. package/docs/integrations/google-tag.md +261 -90
  89. package/docs/integrations/heap.md +222 -149
  90. package/docs/integrations/hightouch.md +225 -131
  91. package/docs/integrations/hotjar.md +239 -90
  92. package/docs/integrations/intercom.md +239 -98
  93. package/docs/integrations/linkedin-insights.md +243 -113
  94. package/docs/integrations/logrocket.md +241 -123
  95. package/docs/integrations/matomo-analytics.md +256 -111
  96. package/docs/integrations/meta-pixel.md +197 -324
  97. package/docs/integrations/microsoft-clarity.md +233 -114
  98. package/docs/integrations/microsoft-uet.md +245 -110
  99. package/docs/integrations/mixpanel-analytics.md +252 -87
  100. package/docs/integrations/openai-pixel.md +441 -0
  101. package/docs/integrations/overview.md +95 -133
  102. package/docs/integrations/pirsch.md +249 -96
  103. package/docs/integrations/plausible-analytics.md +241 -100
  104. package/docs/integrations/posthog.md +353 -214
  105. package/docs/integrations/promptwatch.md +251 -81
  106. package/docs/integrations/reddit-pixel.md +226 -173
  107. package/docs/integrations/rudderstack.md +244 -187
  108. package/docs/integrations/rybbit-analytics.md +244 -91
  109. package/docs/integrations/segment.md +238 -92
  110. package/docs/integrations/snapchat-pixel.md +240 -110
  111. package/docs/integrations/tiktok-pixel.md +249 -81
  112. package/docs/integrations/umami-analytics.md +242 -95
  113. package/docs/integrations/vercel-analytics.md +242 -90
  114. package/docs/integrations/x-pixel.md +238 -104
  115. package/docs/integrations/youtube.md +354 -142
  116. package/docs/upgrade-v3.md +334 -0
  117. package/iab/styles.css +1 -1
  118. package/iab/styles.tw3.css +1 -1
  119. package/package.json +106 -65
  120. package/readme.json +3 -3
  121. package/src/iab/styles.css +1 -1
  122. package/src/iab/styles.tw3.css +1 -1
  123. package/src/styles.css +1 -1
  124. package/src/styles.tw3.css +1 -1
  125. package/styles.css +1 -1
  126. package/styles.tw3.css +1 -1
  127. package/client/components/consent-dialog-link.js +0 -3
  128. package/client/components/integrations.js +0 -3
  129. package/dist/components/integrations/index.cjs +0 -1
  130. package/dist/components/integrations/index.js +0 -1
  131. package/dist/headless.cjs +0 -1
  132. package/dist/index.cjs +0 -1
  133. package/dist/libs/browser-initial-data.cjs +0 -1
  134. package/dist/libs/browser-initial-data.js +0 -1
  135. package/dist/libs/initial-data.cjs +0 -1
  136. package/dist/libs/initial-data.js +0 -1
  137. package/dist/types.cjs +0 -1
  138. package/dist/version.cjs +0 -1
  139. package/dist-types/components/integrations/index.d.ts +0 -1
  140. package/dist-types/libs/browser-initial-data.d.ts +0 -9
  141. package/dist-types/libs/initial-data.d.ts +0 -33
  142. package/docs/frameworks/next/building-headless-components.md +0 -379
  143. package/docs/frameworks/next/callbacks.md +0 -186
  144. package/docs/frameworks/next/concepts/client-modes.md +0 -177
  145. package/docs/frameworks/next/concepts/consent-models.md +0 -117
  146. package/docs/frameworks/next/concepts/cookie-management.md +0 -122
  147. package/docs/frameworks/next/concepts/glossary.md +0 -24
  148. package/docs/frameworks/next/concepts/initialization-flow.md +0 -149
  149. package/docs/frameworks/next/concepts/policy-packs.md +0 -230
  150. package/docs/frameworks/next/hooks/use-color-scheme.md +0 -41
  151. package/docs/frameworks/next/hooks/use-consent-manager/checking-consent.md +0 -96
  152. package/docs/frameworks/next/hooks/use-consent-manager/location-info.md +0 -97
  153. package/docs/frameworks/next/hooks/use-consent-manager/setting-consent.md +0 -94
  154. package/docs/frameworks/next/hooks/use-draggable.md +0 -59
  155. package/docs/frameworks/next/hooks/use-focus-trap.md +0 -42
  156. package/docs/frameworks/next/hooks/use-reduced-motion.md +0 -37
  157. package/docs/frameworks/next/hooks/use-ssr-status.md +0 -32
  158. package/docs/frameworks/next/hooks/use-text-direction.md +0 -50
  159. package/docs/frameworks/next/hooks/use-translations.md +0 -55
  160. package/docs/frameworks/next/iab/consent-banner.md +0 -91
  161. package/docs/frameworks/next/iab/consent-dialog.md +0 -129
  162. package/docs/frameworks/next/iab/use-gvl-data.md +0 -21
  163. package/docs/frameworks/next/iframe-blocking.md +0 -106
  164. package/docs/frameworks/next/internationalization.md +0 -207
  165. package/docs/frameworks/next/network-blocker.md +0 -140
  166. package/docs/frameworks/next/policy-packs.md +0 -248
  167. package/docs/frameworks/next/styling/classnames.md +0 -94
  168. package/docs/frameworks/next/styling/color-scheme.md +0 -84
  169. package/docs/frameworks/next/styling/css-variables.md +0 -53
  170. package/docs/frameworks/next/styling/slots.md +0 -94
  171. package/docs/frameworks/next/styling/tailwind.md +0 -137
  172. package/docs/frameworks/next/styling/tokens.md +0 -156
  173. package/docs/shared/concepts/client-modes.md +0 -103
  174. package/docs/shared/concepts/consent-categories.md +0 -41
  175. package/docs/shared/concepts/consent-models.md +0 -72
  176. package/docs/shared/concepts/cookie-management.md +0 -88
  177. package/docs/shared/concepts/glossary.md +0 -24
  178. package/docs/shared/concepts/initialization-flow.md +0 -105
  179. package/docs/shared/concepts/policy-packs.md +0 -225
  180. package/docs/shared/react/components/consent-banner.md +0 -242
  181. package/docs/shared/react/components/consent-dialog-link.md +0 -45
  182. package/docs/shared/react/components/consent-dialog-trigger.md +0 -185
  183. package/docs/shared/react/components/consent-dialog.md +0 -119
  184. package/docs/shared/react/components/consent-manager-provider.md +0 -225
  185. package/docs/shared/react/components/consent-widget.md +0 -121
  186. package/docs/shared/react/components/dev-tools.md +0 -81
  187. package/docs/shared/react/components/frame.md +0 -52
  188. package/docs/shared/react/guides/building-headless-components.md +0 -110
  189. package/docs/shared/react/guides/callbacks.md +0 -89
  190. package/docs/shared/react/guides/headless.md +0 -31
  191. package/docs/shared/react/guides/iframe-blocking.md +0 -65
  192. package/docs/shared/react/guides/internationalization.md +0 -123
  193. package/docs/shared/react/guides/network-blocker.md +0 -72
  194. package/docs/shared/react/guides/optimization.md +0 -44
  195. package/docs/shared/react/guides/policy-packs.md +0 -173
  196. package/docs/shared/react/guides/script-loader.md +0 -311
  197. package/docs/shared/react/hooks/use-color-scheme.md +0 -31
  198. package/docs/shared/react/hooks/use-consent-manager/checking-consent.md +0 -95
  199. package/docs/shared/react/hooks/use-consent-manager/location-info.md +0 -96
  200. package/docs/shared/react/hooks/use-consent-manager/overview.md +0 -74
  201. package/docs/shared/react/hooks/use-consent-manager/setting-consent.md +0 -93
  202. package/docs/shared/react/hooks/use-draggable.md +0 -30
  203. package/docs/shared/react/hooks/use-focus-trap.md +0 -20
  204. package/docs/shared/react/hooks/use-reduced-motion.md +0 -33
  205. package/docs/shared/react/hooks/use-ssr-status.md +0 -16
  206. package/docs/shared/react/hooks/use-text-direction.md +0 -38
  207. package/docs/shared/react/hooks/use-translations.md +0 -15
  208. package/docs/shared/react/iab/consent-banner.md +0 -60
  209. package/docs/shared/react/iab/consent-dialog.md +0 -76
  210. package/docs/shared/react/iab/overview.md +0 -80
  211. package/docs/shared/react/iab/use-gvl-data.md +0 -21
  212. package/docs/shared/react/styling/classnames.md +0 -93
  213. package/docs/shared/react/styling/color-scheme.md +0 -35
  214. package/docs/shared/react/styling/css-variables.md +0 -53
  215. package/docs/shared/react/styling/overview.md +0 -261
  216. package/docs/shared/react/styling/slots.md +0 -93
  217. package/docs/shared/react/styling/stylesheet-entrypoint.md +0 -8
  218. package/docs/shared/react/styling/tailwind.md +0 -88
  219. package/docs/shared/react/styling/tokens.md +0 -155
  220. package/docs/shared/troubleshooting.md +0 -82
@@ -0,0 +1,416 @@
1
+ ---
2
+ title: Fetching reference
3
+ description: Reference for Next.js consent URLs, manifest resolution, request
4
+ geography and offline configuration.
5
+ group: frameworks
6
+ ---
7
+
8
+ ## URL configuration
9
+
10
+ `defineConsentConfig` from `c15t/next` declares the URLs shared by
11
+ `resolveConsent` and `ConsentRoot`. For help choosing a setup, start with
12
+ [data fetching](../data-fetching.md).
13
+ Requires Next.js 15 or 16 (`next ^15.0.0 || ^16.0.0`).
14
+
15
+ | Property | Behavior |
16
+ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
17
+ | `backendURL` | Required backend base URL. Consent submissions go to `/subjects` under this URL. Without `manifestURL`, server prefetch calls its `/init` endpoint. |
18
+ | `manifestURL` | Optional URL that serves the policy manifest. Setting it enables manifest resolution. |
19
+ | `initURL` | Optional browser initialization endpoint. With the local manifest handler, this resolves policy using the browser request's geography. |
20
+
21
+ The current API does not derive `manifestURL` or `initURL` from `backendURL`,
22
+ and declaring URLs does not create routes. Values can be absolute HTTP or
23
+ HTTPS URLs or paths beginning with `/`. Server helpers resolve relative paths
24
+ against the incoming request's host.
25
+
26
+ `defineConsentConfig` validates at call time and throws a `TypeError` when the
27
+ argument is not an object, when `backendURL` is missing, or when any URL is
28
+ neither an absolute `http(s)` URL nor a `/`-relative path. Protocol-relative
29
+ `//consent.example.com` and bare `api/c15t` are rejected because the server
30
+ helpers would resolve them against the request host and reach an unintended
31
+ origin. The returned object is frozen. When `initURL` is set without
32
+ `manifestURL` and `NODE_ENV` is not `production`, it logs a `console.warn`:
33
+ browser initialization would use the init route, but server prefetch would
34
+ still call backend `/init` on every request. Set `manifestURL` so both sides
35
+ resolve the cached manifest.
36
+
37
+ ### Why are the URLs separate?
38
+
39
+ `backendURL` usually points to Inth, while the manifest and local init routes
40
+ belong to your Next.js application. Appending `/manifest` to the Inth URL would
41
+ select Inth's endpoint, not create or select your app's cached manifest route.
42
+ Using `/api/c15t` as the backend prefix through an optional rewrite does not
43
+ change the config defaults.
44
+
45
+ The manifest **handler** does derive its upstream `/manifest` URL from its
46
+ backend URL. That server-side default is separate from `defineConsentConfig`,
47
+ which keeps the URLs you pass to it and does not choose a fetching mode for you.
48
+
49
+ ### Do I need the local init route?
50
+
51
+ No. The router guides use server prefetch and one manifest route. Successful
52
+ prefetch supplies the initial policy directly to the browser.
53
+
54
+ Add a local init route when browser initialization needs your host's location
55
+ headers or you want to keep manifest resolution on the server. It resolves the
56
+ cached manifest in your app instead of calling the backend's `/init`.
57
+
58
+ Add `initURL: '/api/c15t/init'` to your existing `defineConsentConfig` value,
59
+ keeping `backendURL` and `manifestURL`. Then add the handler for your router.
60
+ It reads the same absolute upstream backend environment variable as your
61
+ manifest handler.
62
+
63
+ ```ts title="app/api/c15t/init/route.ts"
64
+ export { GET } from 'c15t/next/api';
65
+ ```
66
+
67
+ For Pages Router, use this file instead:
68
+
69
+ ```ts title="pages/api/c15t/init.ts"
70
+ import { createPagesApiHandlers } from 'c15t/next/pages';
71
+
72
+ export default createPagesApiHandlers().init;
73
+ ```
74
+
75
+ Without `initURL`, browser initialization resolves the manifest locally. It
76
+ uses location supplied by prefetch or your application, but cannot read your
77
+ host's geographic headers itself. See [geography and privacy signals](#geography-and-privacy-signals).
78
+
79
+ ### Transport combinations
80
+
81
+ | Consent config | Server prefetch | Browser initialization |
82
+ | -------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------ |
83
+ | `backendURL`, `manifestURL`, `initURL` | Resolves the manifest with request inputs | Calls `initURL`, whose local handler resolves the manifest with request inputs |
84
+ | `backendURL`, `manifestURL` | Resolves the manifest with request inputs | Loads the manifest resolver lazily in the browser |
85
+ | `backendURL` only | Calls backend `/init` | Calls backend `/init` |
86
+ | `backendURL`, `initURL` | Calls backend `/init` | Calls `initURL` |
87
+ | Explicit `options.mode: offline()` | No hosted prefetch | Resolves bundled local rules |
88
+
89
+ Hosted combinations submit choices to `${backendURL}/subjects`. Offline mode
90
+ uses browser persistence only. A prepared server result can satisfy the first
91
+ browser initialization; the browser column describes what happens when
92
+ initialization is needed, including recovery after prefetch fails.
93
+
94
+ Setting only `initURL` does not enable server manifest resolution. Keep
95
+ `manifestURL` when both server and browser requests should resolve the cached
96
+ manifest.
97
+
98
+ ### Switch to regular backend init
99
+
100
+ Remove `manifestURL` and `initURL` from `c15t.config.ts`. The
101
+ [browser initialization guide](../client-side.md#configure-one-backend-url)
102
+ has a complete backend-only configuration. For server rendering, keep the
103
+ prefetch and boundary wiring from your router guide. Prefetch will call backend
104
+ `/init` per request.
105
+
106
+ Remove unused local manifest and init handlers. If you use an optional backend
107
+ rewrite, removing the local init handler lets that request reach the backend
108
+ through the rewrite. See [optimization](../optimization.md)
109
+ for rewrite configuration.
110
+
111
+ ## Server helper options
112
+
113
+ `resolveConsent` is the one server helper. `c15t/next/server` exports it for
114
+ the App Router, where the default request context reads `next/headers` and
115
+ calls `await connection()` from `next/server` before reading the clock.
116
+ `c15t/next/pages` exports the same function for the Pages Router, taking the
117
+ Node `req` instead. Both entries accept the options in this section; the
118
+ Pages Router entry replaces only the `request` adapter.
119
+
120
+ What `resolveConsent` does depends on whether it has a backend to reach. With
121
+ `config` or `backendURL`, it resolves policy from the manifest or backend
122
+ `/init` and returns the visitor's `ConsentState`. Without either, it makes no
123
+ network request and returns cookie- and header-only state; see
124
+ [cookie-only state](#cookie-only-state).
125
+
126
+ ### resolveConsent options
127
+
128
+ | Option | Default | Behavior |
129
+ | ---------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
130
+ | `backendURL` | `config.backendURL` | Backend base URL. An explicit value overrides `config.backendURL`. Without a manifest source, `resolveConsent` calls `${backendURL}/init`. Omit both `backendURL` and `config` for cookie-only state. |
131
+ | `config` | none | A `defineConsentConfig` result supplying `backendURL` and `manifestURL`. Explicit options on this bag win over its fields. `initURL` is not read. |
132
+ | `manifestURL` | `config.manifestURL` | Manifest route URL. When set, server prefetch resolves policy from the manifest and does not call backend `/init`. |
133
+ | `manifest` | none | Inline manifest object used instead of fetching `manifestURL`. Any `manifestURL` is still resolved first, so a relative one needs a request host. |
134
+ | `fetch` | `globalThis.fetch` | Fetch implementation for the backend `/init` or manifest request. Wrap it in Next.js fetch caching only in manifest mode; the `/init` call carries visitor cookies and returns per-visitor state, so keep it uncached. |
135
+ | `forwardHeaders` | `[]` | Request header names copied onto the outgoing call. Backend `/init` also forwards `x-forwarded-for` and `user-agent` automatically; manifest requests forward only this list. Headers absent from the request are skipped. |
136
+ | `onError` | none | Receives the failure from the backend or manifest request. When omitted, failures are logged with `console.warn` only when `NODE_ENV` is not `production`. |
137
+ | `now` | `Date.now()` | Clock used to validate stored records and stamped into the result. |
138
+ | `cookieName` | `c15t` | Cookie holding persisted consent. Must match the client `storageConfig.storageKey`. |
139
+ | `country` | header detection | Overrides the country read from request headers. |
140
+ | `language` | header detection | Overrides the language read from `accept-language`. |
141
+ | `request` | `next/headers` | Request context adapter with `cookies()` and `headers()`. The default only works in the App Router. |
142
+
143
+ `resolveConsent` throws when the request adapter's `headers()` or `cookies()`
144
+ rejects (the default adapter rejects outside a request scope), and when your
145
+ `onError` callback throws. Thrown URL resolution, network and policy
146
+ resolution errors are handled: they return the same baseline state as the
147
+ cookie-only call, with stored records, geography, language and GPC from the
148
+ request but no resolved policy. The page still renders and the browser initializes
149
+ consent on mount. Non-2xx responses from backend `/init` are failures too, so
150
+ a `500` renders the baseline rather than throwing. A successful response whose
151
+ body reports `policyResolution.status: 'failed'`, for example an unsupported
152
+ policy contract, is different: that failed resolution is kept as the prepared
153
+ state, the browser does not re-initialize on mount, and the consent UI stays
154
+ hidden until the cause is fixed.
155
+
156
+ Absolute `http(s)` URLs are used as given. A `/`-relative `backendURL` or
157
+ `manifestURL` is resolved from the request: the scheme comes from
158
+ `x-forwarded-proto` and defaults to `https`; the host comes from
159
+ `x-forwarded-host`, then `host`, then the `referer` host. When no host is available, `resolveConsent` reports the error
160
+ and returns the baseline state. Those headers decide where the server sends
161
+ the request, so behind a proxy the edge must overwrite `x-forwarded-host` and
162
+ `x-forwarded-proto` rather than pass client values through; otherwise a client
163
+ can point server prefetch at a host of its choosing. When you cannot guarantee
164
+ that, use absolute URLs. Use `https` for any production `backendURL`: the
165
+ `/init` call forwards the visitor's cookies, which plain HTTP exposes on the
166
+ path; keep `http` for local development only.
167
+
168
+ Forwarded headers differ by path. The backend `/init` call carries the
169
+ request cookies, `x-forwarded-for`, `user-agent`, any `forwardHeaders`, and
170
+ derived `x-c15t-country`, `x-c15t-region`, `accept-language` and `sec-gpc`
171
+ headers; it is sent with `cache: 'no-store'` and `credentials: 'include'`.
172
+ The manifest request carries only the headers named in `forwardHeaders`.
173
+ Cookies, the client IP and the user agent are not added automatically, because
174
+ the manifest is public policy data, but an explicit entry is forwarded
175
+ verbatim: a `forwardHeaders` list that names `cookie`, `x-forwarded-for` or
176
+ `user-agent` sends them to the manifest host too, so do not reuse a list meant
177
+ for the backend path. Cookies are still read locally on both paths to restore
178
+ records.
179
+
180
+ An inline `manifest` is never refreshed. The manifest transport returns the
181
+ object as given instead of fetching it, so the snapshot is the source of truth
182
+ for that request rather than a cache seed. A stale snapshot resolves stale
183
+ policy until the application ships a new one. One request can still happen:
184
+ when the inline manifest has `iab.enabled: true` with an `iab.gvl` reference
185
+ and the matched rule uses the `iab` model, the transport fetches the Global
186
+ Vendor List with the `fetch` option, and a blocked network there also falls
187
+ back to the baseline. `backendURL` is still required because choices post to
188
+ `${backendURL}/subjects`.
189
+
190
+ `onError` replaces the default logging entirely. Without it, production
191
+ deployments render the baseline silently; pass `onError` to report failures to
192
+ your monitoring. `cookieName` must match the client `storageKey` for stored
193
+ choices to be restored at all: with a mismatch the server supplies empty
194
+ records, the provider treats them as prepared and skips browser hydration, so
195
+ the visitor's existing choice stays ignored for the whole mount, not only at
196
+ first paint.
197
+
198
+ ### Cookie-only state
199
+
200
+ Call `resolveConsent` with no `config` and no `backendURL` when rendering
201
+ needs the visitor's stored records but not a resolved policy. It makes no
202
+ network request. It reads the request and returns a JSON-serializable
203
+ `ConsentState` with `initialRecords`, `initialPrivacySignals.gpc`, `now` and,
204
+ when any value was detected, `initialOverrides` with `country`, `region` and
205
+ `language`. It does not set cookies and does not cache across requests. Only
206
+ these options apply:
207
+
208
+ | Option | Default | Behavior |
209
+ | ------------ | ---------------- | --------------------------------------------------------------------------------------------------- |
210
+ | `now` | `Date.now()` | Clock used to validate stored records and stamped into the result. |
211
+ | `cookieName` | `c15t` | Cookie holding persisted consent. Must match the client `storageConfig.storageKey`. |
212
+ | `country` | header detection | Overrides the country read from request headers. |
213
+ | `language` | header detection | Overrides the language read from `accept-language`. |
214
+ | `request` | `next/headers` | Request context adapter with `cookies()` and `headers()`. The default only works in the App Router. |
215
+
216
+ The cookie header is read from `headers().get('cookie')` first and from
217
+ `request.cookies()` only when that header is absent. Country and region come
218
+ from the headers listed in [geography and privacy signals](#geography-and-privacy-signals).
219
+
220
+ ### Types
221
+
222
+ | Type | Exported from | What it names |
223
+ | ---------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------ |
224
+ | `ConsentState` | `c15t/next`, `c15t/next/server`, `c15t/next/pages` | The value `resolveConsent` returns and `ConsentRoot` takes as `state` |
225
+ | `ResolveConsentOptions` | `c15t/next/server` | The App Router options bag |
226
+ | `ConsentRequestOptions` | `c15t/next/server` | The request-reading subset: `now`, `cookieName`, `country`, `language`, `request` |
227
+ | `PagesResolveConsentOptions` | `c15t/next/pages` | `ResolveConsentOptions` with `req` in place of `request` |
228
+ | `ConsentRootProps` | `c15t/next` | Props of `ConsentRoot`; `ConsentRootProps['state']` also accepts the pending promise |
229
+ | `ConsentConfig` | `c15t/next`, `c15t/next/server`, `c15t/next/pages` | The frozen `defineConsentConfig` result passed as `config` |
230
+
231
+ ### Pages Router differences
232
+
233
+ `c15t/next/pages` exports `resolveConsent` with the Node request in place of
234
+ the `request` adapter. `resolveConsent({ req, ...options })` accepts every
235
+ option in the App Router table except `request`; `req` is the request from
236
+ `getServerSideProps` or an API route. Omitting `config` and `backendURL`
237
+ returns cookie-only state here too. The result is JSON-compatible, but fields
238
+ that were not detected are `undefined` (for example
239
+ `initialPrivacySignals.gpc` without a `sec-gpc` header), and
240
+ `getServerSideProps` rejects `undefined` values in development. Round-trip the
241
+ result through `JSON.parse(JSON.stringify(result))` before returning it as a
242
+ prop, as `examples/nextjs/pages/pages-router.tsx` does.
243
+
244
+ `createPagesRequestContext(req)` builds the `request` adapter itself. Headers
245
+ are converted to Web `Headers`, and cookies are read from the `cookie` header.
246
+ Use it when calling the `c15t/next/server` helper from a custom server or
247
+ test harness where `next/headers` is unavailable:
248
+
249
+ ```ts title="server/consent.ts"
250
+ import { resolveConsent } from 'c15t/next/server';
251
+ import { createPagesRequestContext } from 'c15t/next/pages';
252
+ import { consentConfig } from '../c15t.config';
253
+ import type { IncomingMessage } from 'node:http';
254
+
255
+ export function resolveRequestConsent(req: IncomingMessage) {
256
+ return resolveConsent({
257
+ config: consentConfig,
258
+ request: createPagesRequestContext(req),
259
+ });
260
+ }
261
+ ```
262
+
263
+ ## Route handler options
264
+
265
+ ### createNextConsentRouteHandlers options
266
+
267
+ `createNextConsentRouteHandlers(optionsOrConfig)` from `c15t/next/api` returns
268
+ `GET` for the init route and `manifestGET` for the manifest route. The
269
+ ready-made `GET` and `manifestGET` exports from the same module are created
270
+ with no options, so they read only environment variables.
271
+
272
+ | Option | Default / env | Behavior |
273
+ | --------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
274
+ | `backendURL` | `C15T_BACKEND_URL`, then `NEXT_PUBLIC_C15T_BACKEND_URL` | Backend base URL. The manifest route fetches `${backendURL}/manifest`. Not read when `manifestURL` is set. |
275
+ | `manifestURL` | `C15T_MANIFEST_URL` | Full upstream manifest URL. Takes precedence over `backendURL` plus `/manifest`. |
276
+ | `manifestRevalidateSeconds` | `300`; env `C15T_MANIFEST_REVALIDATE_SECONDS` | Next.js Data Cache revalidation for the manifest fetch. `false` disables it; the env value `'false'` also disables it. The env value is read with `parseInt`, so `60seconds` becomes `60` and `1e2` becomes `1`; values without a leading number or below `0` fall back to the default. |
277
+ | `fetch` | `globalThis.fetch` | Fetch implementation for the manifest and Global Vendor List requests. |
278
+ | `onBackgroundRevalidate` | none | Receives the promise of a background manifest refresh started by a request, so a host can keep it alive with `after` from `next/server` or a platform `waitUntil`. Called inside the handler; the promise never rejects. See [Optimization](../optimization.md#configure-manifest-cache-refresh). |
279
+ | `fetchGvl` | built-in cached fetcher | Loads the Global Vendor List for IAB policies. Called only under the conditions described in this section. |
280
+
281
+ An explicit option wins over its environment variable, and a manifest URL from
282
+ either source wins over `backendURL`. URLs are resolved per request, so a
283
+ missing or invalid value fails the request rather than the build: with neither
284
+ `backendURL` nor `manifestURL` configured the handler throws
285
+ `@c15t/nextjs/api: configure C15T_BACKEND_URL or C15T_MANIFEST_URL.`. A
286
+ `/`-relative value is resolved against the incoming request's host and
287
+ forwarded headers, which points the handler at your own app and lets a client
288
+ that controls `x-forwarded-host` choose the upstream. Keep upstream URLs
289
+ absolute so the manifest route neither fetches itself nor an attacker's host.
290
+
291
+ Passing a `defineConsentConfig` result uses only its `backendURL`. Its
292
+ `manifestURL` and `initURL` name the routes these handlers serve, so
293
+ forwarding them would make the manifest route fetch itself. The same rule
294
+ applies when the config's `backendURL` is a same-origin rewrite prefix: pass
295
+ the absolute upstream URL instead, as described in
296
+ [optimization](../optimization.md).
297
+
298
+ `GET` resolves the cached manifest with the request's geography, language and
299
+ GPC headers and responds with `cache-control: private, no-store` and
300
+ `x-c15t-policy-contract: 1`. When the request carries an
301
+ `x-c15t-policy-contract` header with a different value, the response keeps the
302
+ translations and UI data but sets `policyResolution` to `status: 'failed'`
303
+ with `reason: 'unsupported-contract'` and removes `policySnapshotToken` and
304
+ `gvl`. Requests without the header are treated as compatible.
305
+
306
+ `manifestGET` forwards the upstream `cache-control` header, or
307
+ `public, s-maxage=300, stale-while-revalidate=86400` when the upstream omits
308
+ it, plus `etag` when present and an `age` computed from the in-process cache.
309
+ It adds `content-type: application/json`, `x-c15t-policy-contract: 1` and
310
+ `x-c15t-next-revalidate`, whose value is the upstream `s-maxage` when present,
311
+ `300` when the upstream sends no `cache-control` at all (the default header is
312
+ substituted before `s-maxage` is read), and otherwise the configured
313
+ revalidation interval. A `language` query
314
+ parameter on the manifest route is passed to the upstream manifest request.
315
+
316
+ `fetchGvl` runs inside `GET` only when the manifest has `iab.enabled: true`,
317
+ the manifest includes an `iab.gvl` reference, and the resolved policy matched
318
+ with `model: 'iab'`. It receives the reference, the `fetch` option, and the
319
+ language taken from the first segment of the resolved translations language
320
+ (`en` when empty). The default fetcher caches the vendor list in process.
321
+ A fetched list becomes a `gvlReference` and a small banner summary in the
322
+ serialized payload. A `null` result keeps IAB unavailable. With
323
+ `config.initURL`, the browser reads the list from that same-origin handler;
324
+ otherwise it uses the manifest's public list URL. See
325
+ [server-rendered vendor data](https://c15t.com/docs/frameworks/javascript/iab/overview#server-rendered-vendor-data)
326
+ for loading and caching behavior.
327
+
328
+ `createPagesApiHandlers(options)` from `c15t/next/pages` accepts the same
329
+ options or a `defineConsentConfig` result and returns `{ init, manifest }`:
330
+ `init` wraps `GET` and `manifest` wraps `manifestGET`, each taking the Node
331
+ `req` and `res` of a `pages/api` route. Because a `pages/api` default export
332
+ receives every method, requests other than `GET` and `HEAD` are answered with
333
+ `405` and an `allow: GET` header before the wrapped handler runs.
334
+
335
+ ## Manifest request resolution
336
+
337
+ The [App Router](../app-router.md) and
338
+ [Pages Router](../pages-router.md) manifest setups use this flow:
339
+
340
+ 1. The app's manifest handler fetches the absolute backend's `/manifest`
341
+ endpoint and caches public policy data.
342
+ 2. `resolveConsent({ config: consentConfig })` reads the manifest route,
343
+ resolves policy with the current request's inputs and restores valid consent
344
+ cookies.
345
+ 3. `ConsentRoot` receives that result as `state` and the URL configuration
346
+ as `config`. Hydration preserves the resolved state.
347
+ 4. If browser initialization is needed, it resolves the manifest locally. An
348
+ optional `initURL` moves this resolution to the app server.
349
+ 5. Browser choices post to `${backendURL}/subjects`.
350
+
351
+ A warm policy cache avoids backend `/init` during request resolution. The app
352
+ may still read its manifest route, cold caches fetch upstream data, and choices
353
+ still reach the backend. IAB policies can also require a Global Vendor List
354
+ fetch. See [manifest caching](../optimization.md) for cache
355
+ settings, and [server rendering](../server-side.md) for awaiting
356
+ or streaming prefetch results.
357
+
358
+ ## Geography and privacy signals
359
+
360
+ Server manifest resolution reads location headers from the hosting platform.
361
+ It recognizes country headers such as `cf-ipcountry` and
362
+ `x-vercel-ip-country`, and region headers such as `cf-region-code` and
363
+ `x-vercel-ip-country-region`. The application overrides `x-c15t-country` and
364
+ `x-c15t-region` take precedence. Only trusted infrastructure should supply
365
+ location overrides in production.
366
+
367
+ Request helpers also read language and GPC. Missing location stays unknown;
368
+ the resolver does not infer country from the Next.js server's IP. Test unknown
369
+ country and region against your configured policy rules.
370
+
371
+ Browser manifest resolution uses location overrides from prefetch or your
372
+ application. A browser-only entry without those inputs uses unknown location.
373
+ The [optional init route](#do-i-need-the-local-init-route) reads geographic
374
+ headers on the server and keeps resolver code and translations out of browser
375
+ initialization.
376
+
377
+ ## Offline configuration
378
+
379
+ Not recommended for production environments. Use offline mode for local
380
+ development, tests or demos that do not need backend records.
381
+
382
+ Omit hosted prefetch and select the offline factory in a Client Component.
383
+ Keep your existing banner, dialog and preferences link as children of this
384
+ root:
385
+
386
+ ```tsx title="app/offline-consent.tsx"
387
+ 'use client';
388
+
389
+ import type { ReactNode } from 'react';
390
+ import { ConsentRoot, offline } from 'c15t/next';
391
+
392
+ const mode = offline();
393
+
394
+ export function OfflineConsent({ children }: { children: ReactNode }) {
395
+ return (
396
+ <ConsentRoot state={{}} options={{ mode }}>
397
+ {children}
398
+ </ConsentRoot>
399
+ );
400
+ }
401
+ ```
402
+
403
+ The default local policy pack handles missing geography; offline mode does not
404
+ perform IP lookup. Supply `policyRules` to replace that pack when your local
405
+ policy needs different behavior. Browser persistence stores choices, but this
406
+ setup has no backend record service. See
407
+ [transport choices](../../../guides/data-fetching.md) for the tradeoffs.
408
+
409
+ ### Authenticated hosted vendor lists
410
+
411
+ When hosted `resolveConsent()` forwards cookies or additional request headers,
412
+ or uses a custom `fetch`, it retains the fetched vendor list in server state.
413
+ The browser cannot replay a private server fetch. This fallback preserves consent
414
+ loading and vendor filtering without copying credentials into the page. Its
415
+ payload size is unchanged from inline GVL loading. For compact pages with private
416
+ upstreams, expose the public list through a same-origin manifest init handler.