A modern, tree-shakeable analytics library for tracking user events with RudderStack and PostHog. Designed for optimal performance with advanced caching, batching, and offline support.
- 📊 Multi-Provider Support: RudderStack for event tracking and PostHog for analytics & session recording
- 🎄 Tree-Shakeable: Only bundle what you use - each provider can be imported independently
- 📡 Offline-First: Automatic event caching when offline with replay on reconnection
- ⚡ Performance Optimized: Batching, deduplication, and SendBeacon API for fast tracking
- 🔐 Type-Safe: Full TypeScript support with discriminated unions for event payloads
- 🔄 Backward Compatible: Supports older React, Node.js, and other legacy package versions
- 💾 Advanced Caching: Cookie-based and in-memory caching for robust event delivery
- 🎥 Session Recording: Built-in PostHog session recording with customizable settings
Note: GrowthBook support is deprecated and will be removed in a future major version. For A/B testing and feature flags, we recommend using PostHog's built-in feature flag capabilities.
- Installation
- Quick Start
- Framework Integration
- Configuration
- Core API
- Caching & Offline Support
- Advanced Usage
- API Reference
- Performance
- Troubleshooting
- Migration Guide
# Using npm
npm install @deriv-com/analytics
# Using yarn
yarn add @deriv-com/analytics
# Using pnpm
pnpm add @deriv-com/analyticsCore dependencies (@rudderstack/analytics-js, js-cookie, and posthog-js) are installed automatically.
Use directly in browsers without a build tool:
<!-- Load from jsdelivr CDN -->
<script src="https://cdn.jsdelivr.net/npm/@deriv-com/analytics@latest/dist/browser/analytics.bundle.global.js"></script>
<script>
const { Analytics } = window.DerivAnalytics
Analytics.initialise({
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
posthogOptions: {
apiKey: 'YOUR_POSTHOG_KEY',
config: {
autocapture: true,
},
},
}).then(() => {
Analytics.trackEvent('page_view', { page: 'home' })
})
</script>Bundle Size: ~380 KB minified / ~125 KB gzipped (includes RudderStack + PostHog + all dependencies)
import { Analytics } from '@deriv-com/analytics'
// Initialize with RudderStack
await Analytics.initialise({
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
})
// Track events
Analytics.trackEvent('ce_virtual_signup_form', {
action: 'signup_done',
signup_provider: 'email',
})
// Track page views
Analytics.pageView('/dashboard', 'Deriv App')
// Identify users
Analytics.identifyEvent('CR123456')import { Analytics } from '@deriv-com/analytics'
await Analytics.initialise({
// RudderStack for event tracking (required)
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
// PostHog for analytics and session recording (optional)
posthogOptions: {
apiKey: 'phc_YOUR_POSTHOG_KEY',
allowedDomains: ['deriv.com', 'deriv.team', 'deriv.ae'],
config: {
session_recording: {
recordCrossOriginIframes: true,
minimumDurationMilliseconds: 30000,
},
autocapture: true,
},
},
})
// Events are automatically sent to both providers
Analytics.trackEvent('ce_login_form', {
action: 'login_cta',
login_provider: 'google',
})
// User identification syncs with both providers
Analytics.identifyEvent('CR123456', {
language: 'en',
country_of_residence: 'US',
})Create an analytics initialization hook:
// hooks/useAnalytics.ts
import { useEffect } from 'react'
import { Analytics } from '@deriv-com/analytics'
export function useAnalytics() {
useEffect(() => {
Analytics.initialise({
rudderstackKey: process.env.REACT_APP_RUDDERSTACK_KEY!,
posthogOptions: {
apiKey: process.env.REACT_APP_POSTHOG_KEY!,
config: {
autocapture: true,
},
},
})
}, [])
}
// App.tsx
import { useAnalytics } from './hooks/useAnalytics'
function App() {
useAnalytics()
const handleSignup = () => {
Analytics.trackEvent('ce_virtual_signup_form', {
action: 'signup_modal_open',
form_source: 'header_cta',
})
}
return <button onClick={handleSignup}>Sign Up</button>
}// app/providers.tsx
'use client'
import { Analytics } from '@deriv-com/analytics'
import { useEffect } from 'react'
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
Analytics.initialise({
rudderstackKey: process.env.NEXT_PUBLIC_RUDDERSTACK_KEY!,
posthogOptions: {
apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY!,
},
})
}, [])
return <>{children}</>
}
// app/layout.tsx
import { AnalyticsProvider } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<AnalyticsProvider>{children}</AnalyticsProvider>
</body>
</html>
)
}// pages/_app.tsx
import { Analytics } from '@deriv-com/analytics'
import { useEffect } from 'react'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
Analytics.initialise({
rudderstackKey: process.env.NEXT_PUBLIC_RUDDERSTACK_KEY!,
posthogOptions: {
apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY!,
},
})
}, [])
return <Component {...pageProps} />
}// main.ts or main.js
import { createApp } from 'vue'
import { Analytics } from '@deriv-com/analytics'
import App from './App.vue'
// Initialize analytics
Analytics.initialise({
rudderstackKey: import.meta.env.VITE_RUDDERSTACK_KEY,
posthogOptions: {
apiKey: import.meta.env.VITE_POSTHOG_KEY,
},
})
// Make Analytics available globally
const app = createApp(App)
app.config.globalProperties.$analytics = Analytics
app.mount('#app')
// Usage in components
export default {
methods: {
handleClick() {
this.$analytics.trackEvent('button_clicked', { button_name: 'submit' })
},
},
}<!doctype html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@deriv-com/analytics@latest/dist/browser/analytics.bundle.global.js"></script>
</head>
<body>
<button id="signup-btn">Sign Up</button>
<script>
const { Analytics } = window.DerivAnalytics
// Initialize
Analytics.initialise({
rudderstackKey: 'YOUR_KEY',
posthogOptions: {
apiKey: 'YOUR_POSTHOG_KEY',
},
})
// Track button clicks
document.getElementById('signup-btn').addEventListener('click', () => {
Analytics.trackEvent('ce_signup_button', {
action: 'click',
location: 'header',
})
})
</script>
</body>
</html>RudderStack is used for event tracking and includes performance optimizations:
await Analytics.initialise({
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
})Built-in Performance Features:
- Event Batching: Flushes after 10 events or 10 seconds
- SendBeacon API: Uses
navigator.sendBeaconfor better performance on page unload - Automatic Retry: Failed requests are automatically retried
- Cookie Management: Automatic anonymous ID generation and persistence (2-year cookie lifetime)
- Offline Support: Events are cached when offline and replayed when connection is restored
PostHog provides powerful analytics, session recording, and feature flags:
await Analytics.initialise({
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
posthogOptions: {
// Required: API key
apiKey: 'phc_YOUR_KEY',
// Optional: Domain allowlist for security
allowedDomains: ['deriv.com', 'deriv.team', 'deriv.ae'],
// Optional: PostHog configuration
config: {
// API endpoints (defaults shown)
api_host: 'https://ph.deriv.com',
ui_host: 'https://us.posthog.com',
// Session recording
session_recording: {
recordCrossOriginIframes: true,
maskAllInputs: false,
minimumDurationMilliseconds: 30000, // Only save sessions longer than 30 seconds
},
// Feature capture
autocapture: true, // Automatically capture clicks, form submissions, etc.
capture_pageview: true, // Automatically capture page views
capture_pageleave: true, // Capture when users leave pages
// Console log recording (useful for debugging)
enable_recording_console_log: true,
// Disable features if needed
disable_session_recording: false,
disable_surveys: false,
// Custom event filtering
before_send: event => {
// Custom logic to filter or modify events
return event
},
},
},
})For security, PostHog can be configured to only send events from specific domains:
posthogOptions: {
apiKey: 'phc_YOUR_KEY',
allowedDomains: ['deriv.com', 'deriv.team', 'deriv.ae'],
}Events from app.deriv.com, staging.deriv.team, etc. will be sent. Events from other domains will be blocked.
posthogOptions: {
apiKey: 'phc_YOUR_KEY',
config: {
session_recording: {
// Record content from iframes
recordCrossOriginIframes: true,
// Mask sensitive input fields
maskAllInputs: true,
maskInputOptions: {
password: true,
email: true,
},
// Only save sessions longer than 1 minute
minimumDurationMilliseconds: 60000,
// Sampling (record only 50% of sessions)
sessionRecordingSampleRate: 0.5,
},
},
}Initialize the analytics instance before tracking events:
await Analytics.initialise({
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
posthogOptions: {
apiKey: 'phc_YOUR_POSTHOG_KEY',
config: {
autocapture: true,
},
},
})Track custom events with associated data. Supports both V1 (flat) and V2 (structured) formats:
Analytics.trackEvent('ce_login_form', {
action: 'login_cta',
login_provider: 'email',
form_name: 'main_login',
})Analytics.trackEvent('ce_get_start_page', {
action: 'click',
form_name: 'signup_form',
cta_information: {
cta_name: 'get_started',
section_name: 'hero',
},
event_metadata: {
page_name: '/home',
user_language: 'en',
},
})Identify users and sync traits across analytics providers:
// Identify user with ID only
Analytics.identifyEvent('CR123456')// Send same traits to both RudderStack and PostHog
Analytics.identifyEvent('CR123456', {
language: 'en',
country_of_residence: 'US',
account_type: 'real',
})
// Send different traits to each provider
Analytics.identifyEvent('CR123456', {
rudderstack: {
language: 'en',
custom_field: 'value',
},
posthog: {
language: 'en',
country_of_residence: 'US',
subscription_tier: 'premium',
},
})How it works:
- If you pass a simple object (e.g.,
{ language: 'en' }), the same traits are sent to both providers - If you pass an object with
rudderstackorposthogkeys, provider-specific traits are used - Queues identify calls if provider not yet initialized
- PostHog automatically handles aliasing between anonymous and identified users
Track page navigation:
// Basic page view
Analytics.pageView('/dashboard')
// With custom platform name
Analytics.pageView('/dashboard', 'Deriv Trader')
// With additional properties
Analytics.pageView('/trade', 'Deriv App', {
section: 'multipliers',
instrument: 'forex',
})Note: PostHog automatically captures page views when capture_pageview: true is set in config. Manual page view tracking is primarily for RudderStack.
Set user and context attributes that are automatically included in all events:
Analytics.setAttributes({
country: 'US',
user_language: 'en',
account_type: 'real',
device_type: 'mobile',
account_currency: 'USD',
account_mode: 'demo',
residence_country: 'US',
loggedIn: true,
})All subsequent events will include these attributes automatically.
Clear user session from all providers (e.g., on logout):
Analytics.reset()The package includes robust caching mechanisms to ensure no events are lost:
Events are cached in cookies when:
- RudderStack SDK hasn't loaded yet - Events are stored and replayed once the SDK initializes
- User is offline - Events are queued and sent when connection is restored
// Automatic - no configuration needed
Analytics.trackEvent('button_clicked', { button: 'submit' })
// ↓ If offline or SDK not ready, stored in cookies
// ↓ Automatically sent when online/SDK readyTechnical Details:
- Cookies have a 7-day expiration
- Maximum 10 cached events (oldest events dropped if exceeded)
- Automatic cleanup after successful replay
- SameSite=Lax for security
In addition to cookie caching, events are cached in memory when the user is offline but the SDK is initialized:
// While offline
Analytics.trackEvent('offline_event', { data: 'cached' })
// ↓ Stored in memory
// ↓ Sent immediately when online
// Check offline status
window.addEventListener('online', () => {
// Cached events automatically sent
})Page views are also cached using the same mechanism:
// While SDK is loading
Analytics.pageView('/dashboard')
// ↓ Cached in cookies
// ↓ Replayed once SDK is readyFor complex scenarios requiring more control, use the advanced caching utilities:
import { cacheTrackEvents } from '@deriv-com/analytics'
// Track events with automatic caching before SDK loads
cacheTrackEvents.track({
name: 'ce_login_form',
properties: { action: 'open' },
})
// Add click event listeners with auto-retry
cacheTrackEvents.addEventHandler([
{
element: '.signup-button',
event: {
name: 'ce_button_click',
properties: { button_name: 'signup' },
},
cache: true, // Cache if SDK not ready
},
])
// Track page-specific events
cacheTrackEvents.pageLoadEvent([
{
pages: ['dashboard', 'profile'],
event: {
name: 'ce_page_load',
properties: { page_type: 'authenticated' },
},
},
])
// Automatic pageview tracking
cacheTrackEvents.pageView()Each provider can be used independently for maximum flexibility:
import { Posthog } from '@deriv-com/analytics/posthog'
const posthog = Posthog.getPosthogInstance({
apiKey: 'phc_YOUR_KEY',
allowedDomains: ['deriv.com'],
config: {
autocapture: true,
session_recording: {
recordCrossOriginIframes: true,
},
},
})
// Track events
posthog.capture('button_clicked', { button_name: 'signup' })
// Identify users
posthog.identifyEvent('CR123', { language: 'en' })
// Check feature flags
const isEnabled = posthog.isFeatureEnabled('new-feature')
const variant = posthog.getFeatureFlag('button-color')import { RudderStack } from '@deriv-com/analytics/rudderstack'
const rudderstack = RudderStack.getRudderStackInstance('YOUR_KEY', () => {
console.log('RudderStack loaded')
})
// Track events
rudderstack.track('button_clicked', { button: 'signup' })
// Identify users
rudderstack.identifyEvent('CR123', { language: 'en' })
// Track page views
rudderstack.pageView('/dashboard', 'Deriv App', 'CR123')Access raw provider instances for advanced use cases:
const { tracking, posthog } = Analytics.getInstances()
// Access PostHog directly
if (posthog?.has_initialized) {
posthog.capture('custom_event', { property: 'value' })
// Access PostHog feature flags
const isEnabled = posthog.isFeatureEnabled('new-feature')
}
// Access RudderStack directly
if (tracking?.has_initialized) {
const userId = tracking.getUserId()
const anonId = tracking.getAnonymousId()
}Initialize the analytics instance.
Parameters:
interface Options {
rudderstackKey?: string
posthogOptions?: {
apiKey: string
allowedDomains?: string[]
config?: PostHogConfig
}
}Track a typed event.
Track page navigation.
Link anonymous session to a user ID with optional traits.
Update user attributes that flow to all providers.
Clear user session from all providers.
Get the current user ID.
Get the anonymous user ID.
Access raw provider instances.
- Event tracking: <5ms (average)
- Page view tracking: <3ms (average)
- Initialization: ~200ms (with both providers)
- Offline cache replay: <50ms for 10 events
- Tree-Shaking: Unused providers completely removed from bundle
- Lazy Loading: PostHog loaded dynamically only when configured
- Event Batching: RudderStack batches events (10 events or 10 seconds)
- SendBeacon: Uses
navigator.sendBeaconfor reliable event delivery on page unload - Deduplication: Prevents duplicate events from being sent
Estimated sizes (minified + gzipped):
- Core (RudderStack + PostHog): ~32 KB
- RudderStack Only: ~18 KB
- PostHog Only: ~20 KB
- Browser Bundle (all included): ~125 KB gzipped
- Verify API key: Check that
rudderstackKeyis correct - Check network requests: Open DevTools → Network tab → Look for requests to RudderStack dataplane
- Verify initialization: Run
Analytics.getInstances().tracking.has_initializedin console - Check batching: Events are batched - wait ~10 seconds or send 10 events
- Verify API key: Check that PostHog API key is correct (starts with
phc_) - Check domain allowlist: Verify your domain is in the
allowedDomainslist - Check initialization: Run
Analytics.getInstances().posthog?.has_initializedin console - Verify network requests: Check DevTools for requests to
ph.deriv.comor your PostHog host - Check browser console: Look for PostHog errors or warnings
- Verify config: Ensure
disable_session_recording: false(or omit it) - Check minimum duration: Sessions shorter than
minimumDurationMillisecondsare not saved - Verify domain: Check that PostHog is initialized and domain is allowed
- Check PostHog dashboard: Recordings may take a few minutes to appear
- Check online status: Run
navigator.onLinein console - Verify SDK loaded: Run
Analytics.getInstances().tracking.has_initialized - Check cookies: Look for
rudder_*andanalytics_cached_*cookies in DevTools - Clear cache manually: Clear cookies or run
Analytics.reset()
- identifyEvent signature changed:
// Old (v1.x) - hardcoded traits
Analytics.identifyEvent('CR123')
// New (v2.x) - custom traits
Analytics.identifyEvent('CR123', {
language: 'en',
country_of_residence: 'US',
})
// Or provider-specific
Analytics.identifyEvent('CR123', {
rudderstack: { language: 'en' },
posthog: { language: 'en', country_of_residence: 'US' },
})- GrowthBook deprecated: Migrate to PostHog feature flags
// Old (GrowthBook)
const isEnabled = Analytics.isFeatureOn('new-feature')
// New (PostHog)
const { posthog } = Analytics.getInstances()
const isEnabled = posthog?.isFeatureEnabled('new-feature')Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch
- Write tests for your changes
- Run
npm testandnpm run build - Submit a pull request
MIT
For issues and questions:
- GitHub Issues: https://github.com/binary-com/deriv-analytics/issues
- Documentation: https://github.com/binary-com/deriv-analytics