You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Remove Plausible entirely — tracker, first-party proxies, Stats API integration, and the subscription — and replace it with an exact, replayable pipeline on Cloudflare primitives:
direct-to-D1 raw events → pure session reducer → daily aggregates → the existing /stats UI
Why this shape
Two earlier designs were rejected, and the reasoning is worth keeping:
D1 + Cloudflare Queues (June) — Queues are at-least-once, unordered and concurrently consumed. The resulting machinery (idempotency, DLQ, ordering policy, max_concurrency) buys nothing at a few thousand events/day. D1 handles single-row inserts on the request path fine.
Workers Analytics Engine (July) — WAE's ~90-day retention and sampling mean history can never be recomputed if rollup logic changes, sessions become approximations over a lossy store, and reads need an account-scoped HTTP API token.
Keeping exact raw events in a dedicated D1 database makes every derived table a pure, re-runnable function of the event log. No Queues, Workflows, WAE, or R2.
Migration sequence
Each step is independently deployable.
1. Infra — create the two analytics DBs, add bindings + cron + ANALYTICS_SALT_SECRET, typegen, generate and apply migrations (local, staging, prod)
2. Ingest live, dual-run via server-side tee — ship the beacon route and tracker module, and tee the existing Plausible event proxy into native events. Deliberately do not mount the new tracker yet: during the parity window the only browser emitter stays the Plausible script, so both systems see an identical input stream (two trackers would be adblocked differently and poison the comparison)
3. Rollups — deploy the session reducer and hourly rollup cron; verify analytics_day rows appear
4. Parity window (~1 week) — compare analytics_day against the Plausible dashboard daily. Acceptance: pageviews and visitors within ~10%/day; during the tee window expect within ~2%, since the input stream is shared
5. Read cutover — /stats renders native data
6. Tracker cutover — mount <AnalyticsTracker />, remove the Plausible script tags and inline stub from __root.tsx
7. Backfill history — scripts/backfill-plausible.ts pulls Stats API v2 daily timeseries into analytics_day for dates before native ingest began, plus monthly dimension breakdowns pinned to month-start dates (a documented approximation). Never overwrite dates that have native data. Must happen before cancelling Plausible
8. Teardown — delete src/routes/(api)/prxy/, src/lib/domains/Plausible/, and PLAUSIBLE_* env/secrets/typegen. Decide the static.cloudflareinsights.com preconnect (keep iff Cloudflare Web Analytics RUM stays enabled — free Core Web Vitals, independent of this system). Cancel the subscription; keep the backfill script in-tree for provenance
Guardrails
Plausible's server is AGPLv3 — use it as a behavioral spec only; never port or translate its Elixir code. The browser tracker is MIT, but this plan specifies an independent ~100-line tracker, so no code needs copying.
Do not vendor Plausible/Snowplow/GA4 referrer datasets (GPL / Google provenance). The plan ships a small hand-curated source map instead.
request.cf.botManagement is Enterprise-only and not available here. UA heuristics are the baseline; residual bot noise is a documented limitation.
Cost sanity (Workers Paid)
One Worker request per pageview, ~2 D1 row-writes per event, and 24 cron invocations/day — all noise against the included 10M requests and 50M row-writes per month. Raw events at a few KB/row × 180 days sit well under the 5GB included storage.
Tracking issue for replacing Plausible with a self-hosted, Cloudflare-native analytics system.
Full spec:
docs/superpowers/plans/2026-07-19-cloudflare-native-analytics-final.md— self-contained and marked ready for implementation. This issue tracks execution; the spec stays the source of truth for detail.Goal
Remove Plausible entirely — tracker, first-party proxies, Stats API integration, and the subscription — and replace it with an exact, replayable pipeline on Cloudflare primitives:
Why this shape
Two earlier designs were rejected, and the reasoning is worth keeping:
max_concurrency) buys nothing at a few thousand events/day. D1 handles single-row inserts on the request path fine.Keeping exact raw events in a dedicated D1 database makes every derived table a pure, re-runnable function of the event log. No Queues, Workflows, WAE, or R2.
Migration sequence
Each step is independently deployable.
ANALYTICS_SALT_SECRET, typegen, generate and apply migrations (local, staging, prod)analytics_dayrows appearanalytics_dayagainst the Plausible dashboard daily. Acceptance: pageviews and visitors within ~10%/day; during the tee window expect within ~2%, since the input stream is shared/statsrenders native data<AnalyticsTracker />, remove the Plausible script tags and inline stub from__root.tsxscripts/backfill-plausible.tspulls Stats API v2 daily timeseries intoanalytics_dayfor dates before native ingest began, plus monthly dimension breakdowns pinned to month-start dates (a documented approximation). Never overwrite dates that have native data. Must happen before cancelling Plausiblesrc/routes/(api)/prxy/,src/lib/domains/Plausible/, andPLAUSIBLE_*env/secrets/typegen. Decide thestatic.cloudflareinsights.compreconnect (keep iff Cloudflare Web Analytics RUM stays enabled — free Core Web Vitals, independent of this system). Cancel the subscription; keep the backfill script in-tree for provenanceGuardrails
request.cf.botManagementis Enterprise-only and not available here. UA heuristics are the baseline; residual bot noise is a documented limitation.Cost sanity (Workers Paid)
One Worker request per pageview, ~2 D1 row-writes per event, and 24 cron invocations/day — all noise against the included 10M requests and 50M row-writes per month. Raw events at a few KB/row × 180 days sit well under the 5GB included storage.