Analytics & cookie consent
Overview
The kit ships no analytics by default — nothing loads, no cookie banner appears, the base stays visually virgin. When you configure a provider, its script loads in the browser only after the user grants consent (except Plausible, which is cookieless and consent-exempt). A full consent platform — banner, granular preferences, a cookies page — comes with it.
Everything is config-driven via NEXT_PUBLIC_* env vars. Web-analytics
providers are not mutually exclusive — list more than one and they run
side by side, each independently gated by its own consent category; a Meta
Pixel is available separately for the marketing category.
Enabling a provider
Set NEXT_PUBLIC_ANALYTICS_PROVIDERS to a comma-separated list and fill in
each listed provider's key (see .env.example):
# Plausible — cookieless, privacy-first, loads with NO banner
NEXT_PUBLIC_ANALYTICS_PROVIDERS=plausible
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=example.com
# PostHog — product analytics, requires consent
NEXT_PUBLIC_ANALYTICS_PROVIDERS=posthog
NEXT_PUBLIC_POSTHOG_KEY=phc_xxx
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
# Google Analytics 4 — requires consent
NEXT_PUBLIC_ANALYTICS_PROVIDERS=ga
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# Both at once — perfectly valid, each still gated by its own consent category
NEXT_PUBLIC_ANALYTICS_PROVIDERS=plausible,ga
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=example.com
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# Meta Pixel — marketing category, independent of the analytics providers
NEXT_PUBLIC_META_PIXEL_ID=1234567890
If a provider is listed without its key, the app fails fast at boot (Zod
validation in src/env.ts). The older NEXT_PUBLIC_ANALYTICS_PROVIDER
(singular) still works as a one-provider fallback when the plural is unset,
but new setups should use the plural.
Self-hosting Plausible
Set NEXT_PUBLIC_PLAUSIBLE_SRC to your own instance's script URL (e.g.
https://plausible.example.com/js/script.js) to point at self-hosted
Plausible CE instead of Plausible Cloud. Its origin is automatically
allowlisted in the CSP script-src (analyticsScriptHosts in
src/lib/security/csp.ts) — no manual CSP edit needed. Unlike PostHog/GA,
Plausible's script is rendered server-side as a plain <script>
(src/components/consent/PlausibleScript.tsx), not injected client-side, so
it's present in the initial HTML response for any static "verify
installation" checker (Plausible's own included).
Consent model
Four cookie categories are defined in src/config/consent.config.ts:
- Necessary — always active, consent-exempt (session, CSRF, language, the consent cookie itself).
- Analytics — PostHog and GA require consent; Plausible is exempt.
- Marketing — the Meta Pixel requires consent.
- Preferences — an empty scaffold for optional personalization.
Key rules:
- Opt-in only. Nothing optional loads until the user accepts. Reject is as easy as Accept (one click), per ePrivacy anti-dark-pattern guidance.
- The banner appears only when there is something to consent to. If the only configured provider is Plausible (or nothing is configured), no banner renders. Add PostHog/GA/Meta and the relevant category appears.
- The decision is stored in a first-party
cookie_consentcookie (versioned, ~6 months). BumpconsentConfig.versionto re-prompt everyone. - GA uses Consent Mode v2 (default denied → granted) as defense in depth.
Users withdraw or change consent anytime via Cookie settings in the footer
or the /legal/cookies page.
Tracking custom events
Call track() from any client component — it dispatches to every active provider
(and the Meta Pixel if enabled) and no-ops until the relevant script is
loaded, so you never check consent yourself:
import { track } from "@/lib/analytics";
track("signup_completed", { plan: "pro" });
Pageviews on SPA navigation are handled automatically by
src/components/consent/Analytics.tsx.
CSP
Enabling a provider adds its script host to the Content-Security-Policy
script-src automatically (src/lib/security/csp.ts → analyticsScriptHosts,
wired in next.config.ts). connect-src/img-src already allow https:. This
matters only when CSP_MODE=enforce.
Extending
Provider adapters live in src/lib/analytics/providers/; the dispatch API in
src/lib/analytics/index.ts; provider selection in
src/config/analytics.config.ts; the category catalog and gating helpers in
src/config/consent.config.ts. To register a new marketing pixel, add a
technology to the catalog and an adapter, then render it (gated by its category)
in Analytics.tsx.