@startsimpli/analytics
v0.4.13
Published
Shared analytics and telemetry package for StartSimpli Next.js apps
Maintainers
Readme
@startsimpli/analytics
Shared telemetry layer for the StartSimpli Next.js apps. Wraps Google Analytics 4 (gtag.js) and PostHog (product analytics + feature flags + session recording) behind a single TelemetryService so events, page views, identifies, and feature-flag lookups fan out to every configured provider with the same call. Ships React hooks, a Next-aware <GoogleAnalyticsScript> component, and a reportWebVital handler for Next's useReportWebVitals.
Currently consumed by raise-simpli/web-app/ and market-simpli/ (browser-side only — there is no server SDK here).
Install
Workspace dependency only:
// app/package.json
"dependencies": {
"@startsimpli/analytics": "workspace:*"
}posthog-js is an optional peer dependency — pulled in via dynamic import('posthog-js'). Install it in the consuming app if you want PostHog wired up; GA-only deployments can omit it.
Public surface
| Export | Type | Description |
| --- | --- | --- |
| telemetry | TelemetryService singleton | Global instance; call .initialize() once per page load |
| TelemetryService | class | Constructable for tests / multi-tenant cases |
| trackEvent / trackPageView / identifyUser / resetUser | functions | Singleton convenience wrappers |
| GoogleAnalyticsProvider / PostHogProvider | classes | Direct provider access (custom wiring) |
| gtagEvent / gtagPageView / gtagSetUserId | functions | Low-level gtag helpers |
| AnalyticsEvent / PageViewEvent / UserIdentity / TelemetryConfig / FeatureFlagResult / AnalyticsProvider / EventCategory | types | Public type surface |
| GoogleAnalyticsScript (from /components) | React component | Drop-in Next <Script> injector for GA4 |
| useAnalytics (from /hooks) | hook | Returns bound { track, pageView, identify, reset, isFeatureEnabled } |
| useAnalyticsLifecycle (from /hooks) | hook | Initializes telemetry, fires page views on route change, identifies on auth settle, resets on logout |
| reportWebVital (from /vitals) | function | Handler for Next's useReportWebVitals — rates against Google's thresholds and POSTs to a configurable endpoint in prod |
| WebVital / WebVitalName / ReportWebVitalOptions | types | Web vitals types |
Subpath exports: ., ./providers, ./components, ./hooks, ./vitals.
Event-naming convention
EventCategory is a union of standard buckets plus string (open for app-specific extensions):
navigation | user | search | filter | export | integration | error | <any string>Event names are not enforced by the type system — convention across consuming apps is snake_case_verb_noun (e.g. fundraise_created, button_clicked, pageview_dashboard). Categories are passed alongside as the second argument and become event_category in GA4 and a category property in PostHog.
Configuration
Telemetry reads from NEXT_PUBLIC_* env vars at initialize() time. All are optional — missing keys silently disable that provider.
| Variable | Purpose |
| --- | --- |
| NEXT_PUBLIC_GA_MEASUREMENT_ID | GA4 measurement ID (e.g. G-XXXXXXXXXX) |
| NEXT_PUBLIC_POSTHOG_KEY | PostHog project API key |
| NEXT_PUBLIC_POSTHOG_HOST | PostHog host (defaults to https://us.i.posthog.com) |
| NEXT_PUBLIC_ANALYTICS_ENABLED | Set to 'true' to enable telemetry in non-production builds |
By default telemetry is only enabled in production — set NEXT_PUBLIC_ANALYTICS_ENABLED=true to capture in dev/staging.
You can also pass overrides directly:
telemetry.initialize({
googleAnalytics: { enabled: true, measurementId: 'G-XYZ' },
posthog: { enabled: true, apiKey: 'phc_…', apiHost: 'https://eu.i.posthog.com' },
debug: true,
})Usage
Real wiring from raise-simpli/web-app/src/components/analytics/AnalyticsProvider.tsx:
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
import { useAuth } from '@startsimpli/auth'
import { useAnalyticsLifecycle } from '@startsimpli/analytics/hooks'
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const searchParams = useSearchParams()
const { user, isLoading } = useAuth()
useAnalyticsLifecycle({ pathname, searchParams, user, isLoading })
return <>{children}</>
}And app/layout.tsx (market-simpli):
import { GoogleAnalyticsScript } from '@startsimpli/analytics/components'
export default function RootLayout({ children }) {
return (
<html>
<body>
<GoogleAnalyticsScript measurementId={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!} />
{children}
</body>
</html>
)
}Track an event from anywhere client-side:
'use client'
import { useAnalytics } from '@startsimpli/analytics/hooks'
export function SignupButton() {
const { track } = useAnalytics()
return <button onClick={() => track('button_clicked', 'user', { button: 'signup' })}>Sign up</button>
}Web vitals (market-simpli/src/components/analytics/WebVitalsReporter.tsx):
'use client'
import { useReportWebVitals } from 'next/web-vitals'
import { reportWebVital } from '@startsimpli/analytics/vitals'
export function WebVitalsReporter() {
useReportWebVitals(reportWebVital)
return null
}Verification
cd packages/analytics
pnpm tsc --noEmitThis package currently ships no unit tests — the lifecycle behavior is exercised in the consumer apps (see raise-simpli/web-app/src/__tests__/components/useAnalyticsLifecycle.test.tsx and …/lib/vitals.test.ts). Adding a vitest suite here is tracked as future work.
Shared-first
Per CLAUDE.md rule 9, analytics primitives never live in app src/. If you find an AnalyticsProvider.tsx or a track(…) helper duplicated inside an app, hoist it into @startsimpli/analytics and consume it. Both raise-simpli and market-simpli already follow this pattern — their app-local components are thin wrappers around the hooks here.
