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'functionApp(){return(<AnalyticsProviderconfig={{app: 'my-app',version: 1,debug: true}}>{children}</AnalyticsProvider>)}import{useConsent}from'@vllnt/analytics/react'functionCookieBanner(){const{ hasResponded, isLoading, acceptAll, declineAnalytics }=useConsent()if(isLoading||hasResponded)returnnullreturn(<div><p>We use cookies for analytics.</p><buttononClick={acceptAll}>Accept</button><buttononClick={declineAnalytics}>Decline</button></div>)}import{track,trackPage}from'@vllnt/analytics'// Generic eventtrack('button_click',{id: 'cta-hero'})// Page viewtrackPage({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 loginidentify('user-123',{plan: 'pro'})// After logoutresetAnalytics()import{loadConsent,saveConsent,clearConsent,createAcceptAllConsent,createDeclineAnalyticsConsent,getConsentFromCookie,isDoNotTrackEnabled,needsRePrompt,}from'@vllnt/analytics'// SSR: read consent from cookie headerconstanalyticsAllowed=getConsentFromCookie(request.headers.cookie)// Check if re-prompt needed after version bumpconstconsent=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