Lightweight, privacy-first analytics library with consent management and React integration.
- Event tracking with pre-init queuing (no lost events during hydration)
- Consent management — two categories (analytics + functional), localStorage + SSR cookie
- Versioned consent — bump version to re-prompt users
- Do Not Track — respects browser DNT setting
- SSR-safe — all browser APIs guarded
- Zero external analytics dependencies — bring your own backend (Vercel Analytics, GA, custom)
pnpm add @vllnt/analyticsPeer dependency: react ^18.0.0 || ^19.0.0
import { AnalyticsProvider } from '@vllnt/analytics/react'
function App() {
return (
<AnalyticsProvider config={{ app: 'my-app', version: 1, debug: true }}>
{children}
</AnalyticsProvider>
)
}import { useConsent } from '@vllnt/analytics/react'
function CookieBanner() {
const { hasResponded, isLoading, acceptAll, declineAnalytics } = useConsent()
if (isLoading || hasResponded) return null
return (
<div>
<p>We use cookies for analytics.</p>
<button onClick={acceptAll}>Accept</button>
<button onClick={declineAnalytics}>Decline</button>
</div>
)
}import { track, trackPage } from '@vllnt/analytics'
// Generic event
track('button_click', { id: 'cta-hero' })
// Page view
trackPage({ path: '/about' })Typed helpers for tutorial/content flows:
import {
trackTutorialStart,
trackSectionView,
trackSectionComplete,
trackTutorialComplete,
trackTutorialNavigation,
trackTimeSpent,
} from '@vllnt/analytics'
trackTutorialStart({
tutorial_id: 'getting-started',
tutorial_title: 'Getting Started',
locale: 'en',
total_sections: 5,
})
trackSectionView({
tutorial_id: 'getting-started',
tutorial_title: 'Getting Started',
locale: 'en',
section_id: 'intro',
section_index: 0,
total_sections: 5,
})import { identify, resetAnalytics } from '@vllnt/analytics'
// After login
identify('user-123', { plan: 'pro' })
// After logout
resetAnalytics()import {
loadConsent,
saveConsent,
clearConsent,
createAcceptAllConsent,
createDeclineAnalyticsConsent,
getConsentFromCookie,
isDoNotTrackEnabled,
needsRePrompt,
} from '@vllnt/analytics'
// SSR: read consent from cookie header
const analyticsAllowed = getConsentFromCookie(request.headers.cookie)
// Check if re-prompt needed after version bump
const consent = loadConsent()
if (consent && needsRePrompt(consent, 2)) {
clearConsent()
}| Function | Description |
|---|---|
initAnalytics(config) |
Initialize analytics singleton |
track(name, properties?) |
Track a custom event |
trackPage(properties?) |
Track a page view |
identify(userId, traits?) |
Identify a user |
resetAnalytics() |
Clear user identity (logout) |
enableAnalytics(config) |
Re-enable after consent granted |
disableAnalytics() |
Disable after consent revoked |
isAnalyticsEnabled() |
Check if tracking is active |
| Function | Description |
|---|---|
loadConsent() |
Load consent from localStorage |
saveConsent(state) |
Persist consent to localStorage + cookie |
clearConsent() |
Remove consent (triggers re-prompt) |
createAcceptAllConsent() |
Create accept-all consent state |
createDeclineAnalyticsConsent() |
Create analytics-declined state |
updateConsentCategory(state, category, value) |
Update a single category |
needsRePrompt(consent, version) |
Check if version requires re-prompt |
getConsentFromCookie(cookieString) |
Parse consent from cookie (SSR) |
isDoNotTrackEnabled() |
Check browser DNT setting |
| Export | Description |
|---|---|
AnalyticsProvider |
Context provider — wires consent + analytics init |
useAnalytics() |
Access full analytics context |
useConsent(options?) |
Standalone consent hook for banner UIs |
User visits site
│
├─ DNT enabled? ──▶ Block all tracking
│
├─ Consent exists?
│ ├─ Version outdated? ──▶ Clear & re-prompt
│ ├─ Analytics declined? ──▶ Block tracking
│ └─ Analytics accepted ──▶ Initialize & flush queue
│
└─ No consent ──▶ Queue events until user responds
Events fired before initialization are queued and flushed once consent is granted and analytics initializes.
pnpm test # Run tests (watch mode)
pnpm test:once # Run tests once
pnpm test:coverage # Run with coverage
pnpm lint # Lint
pnpm lint:fix # Lint + autofix