analytics

Count page views and button clicks, and send them to the analytics service you choose.

Analytics & errorsClientBuilt on Google Analytics, Plausible, PostHog

kitr/analytics gives you one small API (page, track, identify, reset) and sends each call to whichever services you configure: Google Analytics, Plausible, PostHog, or several at once. Page views are counted for you on every route change. Switching providers later is a change to one config file, not to every button.

Install

kitr add analytics

First time? Run npm i -g kitr once. Inside a project, the global kitr runs your project's own version.

It writes config/analytics.ts and app/analytics.ts, and adds an optional VITE_GA_MEASUREMENT_ID to your env.

Until you add a service, development logs every event to the console (and to devtools), and production sends nothing. The app works before you've chosen a vendor.

Choose where events go

// config/analytics.ts
import { defineConfig, googleAnalytics, plausible } from 'kitr/analytics'
import { env } from '#app/env'

export default defineConfig({
  adapters: [
    ...(env.VITE_GA_MEASUREMENT_ID
      ? [googleAnalytics({ measurementId: env.VITE_GA_MEASUREMENT_ID })]
      : []),
    plausible({ domain: 'example.com' }),
  ],
  globalProps: { app: env.VITE_APP_NAME },
})
Adapter Setup
googleAnalytics({ measurementId }) Loads gtag for you.
plausible({ domain, apiHost }) Loads the Plausible script for you.
posthog(posthogJs, { apiKey }) Pass the posthog-js module, installed with pnpm add posthog-js.
consoleAdapter() Logs every call.
memoryAdapter() Keeps calls in an array, for tests.

An adapter is an object with page and track, plus optional init, identify and reset. Writing one for another service takes a few lines.

Option Default Description
adapters none Where events go.
enabled true Set false to hold events until analytics.enable(), for consent banners.
pageViews true Send a page view on every route change.
globalProps none Added to every event, such as the app version.
debug development Log every call to the console.

Track events

Declare your events once, with their properties:

// app/analytics.ts
import type { EventMap } from 'kitr/analytics'

export interface AppEvents extends EventMap {
  'sign-in.completed': { method: 'password' | 'oauth' }
  'project.created': { id: string }
  'upgrade.clicked': undefined
}

Then track them from components. A misspelt event name or a missing property is a type error:

import { useAnalytics } from 'kitr/analytics/react'
import type { AppEvents } from '#app/analytics'

function CreateButton() {
  const analytics = useAnalytics<AppEvents>()
  return (
    <button onClick={async () => {
      const project = await create()
      analytics.track('project.created', { id: project.id })
    }}>
      New project
    </button>
  )
}

After sign-in, call analytics.identify(user.id, { plan }). On sign-out, call analytics.reset().

Start with enabled: false. Calls made before consent wait in a queue, up to 100 of them. When the visitor agrees, call analytics.enable() and the queue is sent. analytics.disable() stops sending again.

Safe by default

Every adapter call is wrapped, so a blocked script or a failing vendor never breaks your app. On the server nothing is sent.