Cloudflare Analytics Engine

Configure first-party analytics ingestion on Cloudflare and query a semantic event shape on top of Analytics Engine slots.

Use Cloudflare Analytics Engine when you want first-party ingestion on Cloudflare without managing your own event store. ViteHub keeps the public runtime API on @vitehub/analytics, routes browser-side helpers through a generated ingestion endpoint, and writes normalized server-side events into a bound dataset.

Before you start

Cloudflare Analytics Engine does not need an extra npm package for @vitehub/analytics, but it does require a Worker binding.

  • Configure an analytics_engine_datasets binding in the generated Wrangler config through analytics.cloudflareAnalyticsEngine.
  • Expect Analytics Engine to stay ingestion-focused. Querying happens through Cloudflare's SQL API, not through @vitehub/analytics.
  • Analytics Engine retains data for three months and uses sampled queries for large reads.

Configure Cloudflare Analytics Engine

Set analytics.provider to cloudflare-analytics-engine, then configure the dataset under analytics.cloudflareAnalyticsEngine.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@vitehub/analytics/nuxt'],
  analytics: {
    provider: 'cloudflare-analytics-engine',
    cloudflareAnalyticsEngine: {
      dataset: 'analytics_events',
    },
  },
})

Cloudflare-specific options

Keep Analytics Engine settings in top-level analytics.cloudflareAnalyticsEngine config.

OptionUse it for
bindingOverride the Worker binding name used at runtime. Defaults to ANALYTICS.
datasetName the Analytics Engine dataset bound to the Worker. Required.

Track events

Use the shared runtime helpers on the client or the server. Browser-side calls always post to ViteHub's generated ingestion route. Server-side calls write directly to the bound Analytics Engine dataset.

server/api/signup.post.ts
import { group, track } from '@vitehub/analytics'

export default defineEventHandler(async () => {
  await group('team_123', { seats: 5 })
  await track('signup', { plan: 'pro' })

  return { ok: true }
})

Semantic event shape

Treat the raw Analytics Engine slots as a storage detail. ViteHub writes a fixed event schema for new rows and recommends querying it through semantic aliases.

Semantic fieldMeaning
sample_keyStable sampling key derived from userId, groupId, page path, event name, or operation.
operationHelper kind such as track, page, identify, alias, group, or reset.
event_nameCustom event name from track(). Empty for other helpers.
page_pathPage path from page({ path }). Empty for other helpers.
runtimeclient or server.
subject_kinduser, group, or none.
subject_iduserId or groupId when present.
schema_versionFixed version marker. New rows use v2.
metadata_jsonRemaining helper payload such as extra data, traits, or previousId.
event_countConstant 1, useful for counts.

For v2 rows, the raw slot layout is:

Raw slotSemantic field
index1sample_key
blob1operation
blob2event_name
blob3page_path
blob4runtime
blob5subject_kind
blob6subject_id
blob7schema_version
blob8metadata_json
double1event_count

Query the dataset

ViteHub does not abstract reads. Query Analytics Engine directly through Cloudflare's SQL API, but keep one semantic SELECT as your query layer so the rest of your queries do not depend on blobN positions.

WITH analytics_events_semantic AS (
  SELECT
    index1 AS sample_key,
    blob1 AS operation,
    CASE
      WHEN blob7 = 'v2' THEN blob2
      WHEN blob1 = 'track' THEN blob2
      ELSE ''
    END AS event_name,
    CASE
      WHEN blob7 = 'v2' THEN blob3
      WHEN blob1 = 'page' THEN blob2
      ELSE ''
    END AS page_path,
    CASE
      WHEN blob7 = 'v2' THEN blob4
      ELSE blob3
    END AS runtime,
    CASE
      WHEN blob7 = 'v2' THEN blob5
      ELSE ''
    END AS subject_kind,
    CASE
      WHEN blob7 = 'v2' THEN blob6
      ELSE ''
    END AS subject_id,
    CASE
      WHEN blob7 = 'v2' THEN blob8
      ELSE blob4
    END AS metadata_json,
    CASE
      WHEN blob7 = 'v2' THEN blob7
      ELSE 'v1'
    END AS schema_version,
    double1 AS event_count
  FROM analytics_events
)
SELECT
  event_name,
  SUM(event_count) AS events
FROM analytics_events_semantic
WHERE operation = 'track'
GROUP BY event_name
ORDER BY events DESC
LIMIT 20

The compatibility layer above reads both:

  • v2 rows from the current fixed slot schema
  • older v1 rows where blob2 stored the event label or page path, blob3 stored runtime, and blob4 stored the JSON envelope

What changes on Cloudflare

ConcernBehavior
Browser ingestionClient-side helpers always post to /_vitehub/analytics/track or your overridden analytics.client.base.
Server ingestionNitro writes directly to the Analytics Engine binding.
StorageViteHub does not use D1 for analytics persistence anymore.
Native handlegetAnalytics().native exposes binding and dataset.
QueryingReads stay in Cloudflare through the Analytics Engine SQL API.
Choose Cloudflare Analytics Engine when the app runs on Cloudflare and you want one shared event API with a first-party ingestion path.