Edge Deployment
The /init endpoint determines consent policy from geo headers, resolves translations, and optionally fetches the GVL. None of this requires a database. The @c15t/backend/edge export lets you run this logic in edge runtimes (Vercel Middleware, Cloudflare Workers, Deno Deploy) so the consent banner resolves from the nearest PoP instead of round-tripping to your origin.
Info
The edge runtime exports in @c15t/backend/edge are unstable in 2.0. Use the unstable_-prefixed callables and expect API changes or removal in a future release.
When to use this
- Your origin server is in a single region and users are globally distributed
- You want the consent banner to appear as fast as possible (edge latency is typically 10-50ms vs 100-300ms to origin)
- You already use edge middleware for other purposes (auth, redirects, A/B testing)
You do not need this if:
- Your origin is already multi-region or on a platform like Cloudflare Workers
- Consent banner latency is not a concern for your use case
Setup
1. Extract shared config
Keep your policy configuration in a shared file so both the edge handler and origin handler stay in sync:
2. Create edge middleware
3. Keep your origin handler unchanged
The origin API route still handles all database-dependent endpoints (/subjects, /consents, /status). The only difference is that /init requests no longer reach the origin — they're intercepted at the edge.
Configuration
unstable_c15tEdgeInit accepts C15TEdgeOptions — the same fields as c15tInstance minus the database-related options (adapter, tablePrefix, basePath, openapi, ipAddress, apiKeys, background).
| Option | Required | Description |
|---|---|---|
trustedOrigins | Yes | Allowed CORS origins — must match your origin handler |
policyPacks | No | Regional policy configuration |
policySnapshot | No | Signing key for policy snapshot tokens |
iab | No | IAB TCF configuration |
i18n | No | Translation profiles |
branding | No | Banner branding (default: "c15t") |
appName | No | Application name (default: "c15t") |
tenantId | No | Tenant ID for multi-tenant deployments |
cache | No | External cache adapter for GVL |
disableGeoLocation | No | Disable geo-location detection |
telemetry | No | OpenTelemetry configuration |
logger | No | Logger configuration |
Info
The edge handler and origin handler must share the same policyPacks, policySnapshot, trustedOrigins, iab, and i18n configuration. If they diverge, /init will return policies that don't match what the database endpoints expect. Use a shared config file as shown above.
How it works
The edge handler:
- Reads geo headers — Vercel sets
x-vercel-ip-countryandx-vercel-ip-country-regionautomatically. Cloudflare setscf-ipcountry. The handler checks all common provider headers. - Resolves jurisdiction — Maps the country/region to a jurisdiction code (GDPR, CCPA, UK_GDPR, etc.)
- Matches a policy pack — Finds the first matching policy for the visitor's location
- Resolves translations — Picks the right language from
Accept-Languageand your i18n config - Signs a snapshot token — Creates a JWT proving which policy was served (if
policySnapshotis configured) - Handles CORS — Validates the
Originheader againsttrustedOriginsand sets appropriate headers
All of this uses the same functions as the full c15tInstance — the edge handler is not a reimplementation, it's the same code without the database layer.
Caching considerations
Edge isolates have short-lived memory. The in-memory GVL cache resets on each cold start. For production:
- Bundle GVL translations using
iab.bundledto avoid fetch latency entirely - Use an external cache (Upstash Redis, Cloudflare KV) via the
cache.adapteroption to share cached data across isolates — see the Caching guide for setup
Custom consent cookie — unstable_resolveConsent
Info
Experimental — this API may change in future versions.
If you manage your own consent cookie and just need to know which categories to load for a given visitor, use unstable_resolveConsent instead of unstable_c15tEdgeInit. It's a lightweight, fully synchronous function that returns the matched policy and default consent state — no translations, GVL, branding, or snapshot tokens.
Default consent by model
| Model | necessary | Other categories | Notes |
|---|---|---|---|
opt-in | granted, required | not granted | Unless listed in preselectedCategories |
opt-out | granted, required | granted | GPC signal can override marketing/measurement to not granted |
none | granted, required | granted | No banner shown |
unstable_resolveConsent vs unstable_c15tEdgeInit
unstable_resolveConsent | unstable_c15tEdgeInit | |
|---|---|---|
| Use case | Custom consent cookie | Drop-in /init replacement |
| Sync | Yes | No (async — signs JWT, fetches GVL) |
| Returns | Policy + default consent state | Full /init JSON payload |
| CORS | Not handled (your middleware) | Built-in |
| Translations | Not included | Included |
| Snapshot token | Not included | Included |
What stays on the origin
Only /init moves to the edge. These endpoints still require the origin server:
POST /subjects— creates/updates consent subjects (needs DB)POST /consents— records consent decisions (needs DB)GET /status— checks current consent status (needs DB)
The edge handler is a single-purpose optimization for the one endpoint that doesn't need persistent storage.