Analytics SDK
React Integration
Use PRIV SDK with React and Next.js applications including hooks, context providers, and App Router patterns.
React Integration
Use PRIV SDK with React and Next.js applications.
Basic Setup with useEffect
The simplest way to use the SDK in React:
'use client'; // Required for Next.js App Router
import { useEffect } from 'react';
import { priv } from '@priv/sdk';
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
// Initialize SDK
priv.init({
apiKey: process.env.NEXT_PUBLIC_PRIV_KEY!,
endpoint: 'https://api.priv.io/v1',
debug: process.env.NODE_ENV === 'development',
});
// Cleanup on unmount
return () => {
priv.shutdown();
};
}, []);
return <>{children}</>;
}Context Provider Pattern
For better React integration, create a context provider:
// lib/analytics-context.tsx
'use client';
import {
createContext,
useContext,
useEffect,
useState,
useCallback,
type ReactNode,
} from 'react';
import { priv } from '@priv/sdk';
import type { EventProperties, UserTraits } from '@priv/sdk';
interface AnalyticsContextType {
track: (event: string, properties?: EventProperties) => void;
page: (name?: string, properties?: EventProperties) => void;
identify: (userId: string, traits?: UserTraits) => void;
setConsent: (granted: boolean) => void;
reset: () => void;
isTracking: boolean;
}
const AnalyticsContext = createContext<AnalyticsContextType | null>(null);
interface AnalyticsProviderProps {
children: ReactNode;
apiKey: string;
endpoint?: string;
}
export function AnalyticsProvider({
children,
apiKey,
endpoint = 'https://api.priv.io/v1',
}: AnalyticsProviderProps) {
const [isTracking, setIsTracking] = useState(false);
useEffect(() => {
priv.init({
apiKey,
endpoint,
debug: process.env.NODE_ENV === 'development',
});
// Check if consent was previously given
setIsTracking(priv.isTracking());
return () => {
priv.shutdown();
};
}, [apiKey, endpoint]);
const track = useCallback((event: string, properties?: EventProperties) => {
priv.track(event, properties);
}, []);
const page = useCallback((name?: string, properties?: EventProperties) => {
priv.page(name, properties);
}, []);
const identify = useCallback((userId: string, traits?: UserTraits) => {
priv.identify(userId, traits);
}, []);
const setConsent = useCallback((granted: boolean) => {
priv.setConsent(granted);
setIsTracking(granted);
}, []);
const reset = useCallback(() => {
priv.reset();
}, []);
return (
<AnalyticsContext.Provider
value={{ track, page, identify, setConsent, reset, isTracking }}
>
{children}
</AnalyticsContext.Provider>
);
}
export function useAnalytics() {
const context = useContext(AnalyticsContext);
if (!context) {
throw new Error('useAnalytics must be used within AnalyticsProvider');
}
return context;
}Next.js App Router Integration
Layout Setup
// app/layout.tsx
import { AnalyticsProvider } from '@/lib/analytics-context';
import { ConsentBanner } from '@/components/consent-banner';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<AnalyticsProvider apiKey={process.env.NEXT_PUBLIC_PRIV_KEY!}>
{children}
<ConsentBanner />
</AnalyticsProvider>
</body>
</html>
);
}Page View Tracking
Track page views automatically on route changes:
// components/page-view-tracker.tsx
'use client';
import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
import { useAnalytics } from '@/lib/analytics-context';
export function PageViewTracker() {
const pathname = usePathname();
const searchParams = useSearchParams();
const { page, isTracking } = useAnalytics();
useEffect(() => {
if (!isTracking) return;
// Track page view with URL info
page(undefined, {
path: pathname,
search: searchParams.toString(),
});
}, [pathname, searchParams, page, isTracking]);
return null;
}Add to your layout:
// app/layout.tsx
import { Suspense } from 'react';
import { PageViewTracker } from '@/components/page-view-tracker';
export default function RootLayout({ children }) {
return (
<html>
<body>
<AnalyticsProvider apiKey={process.env.NEXT_PUBLIC_PRIV_KEY!}>
<Suspense fallback={null}>
<PageViewTracker />
</Suspense>
{children}
</AnalyticsProvider>
</body>
</html>
);
}Consent Banner Component
A simple consent banner using the context:
// components/consent-banner.tsx
'use client';
import { useState, useEffect } from 'react';
import { useAnalytics } from '@/lib/analytics-context';
export function ConsentBanner() {
const { setConsent, isTracking } = useAnalytics();
const [showBanner, setShowBanner] = useState(false);
useEffect(() => {
// Check if consent decision was already made
const hasConsent = localStorage.getItem('analytics-consent');
if (hasConsent === null) {
setShowBanner(true);
}
}, []);
const handleAccept = () => {
localStorage.setItem('analytics-consent', 'true');
setConsent(true);
setShowBanner(false);
};
const handleReject = () => {
localStorage.setItem('analytics-consent', 'false');
setConsent(false);
setShowBanner(false);
};
if (!showBanner) return null;
return (
<div className="fixed bottom-4 right-4 max-w-md bg-white rounded-lg shadow-lg p-6 border">
<h3 className="font-semibold mb-2">We value your privacy</h3>
<p className="text-sm text-gray-600 mb-4">
We use cookies to improve your experience and analyze site usage.
</p>
<div className="flex gap-2">
<button
onClick={handleReject}
className="px-4 py-2 text-sm border rounded hover:bg-gray-50"
>
Reject
</button>
<button
onClick={handleAccept}
className="px-4 py-2 text-sm bg-blue-600 text-white rounded hover:bg-blue-700"
>
Accept
</button>
</div>
</div>
);
}Event Tracking in Components
Track Button Clicks
'use client';
import { useAnalytics } from '@/lib/analytics-context';
export function SignupButton() {
const { track } = useAnalytics();
const handleClick = () => {
track('signup_button_clicked', {
location: 'hero',
variant: 'primary',
});
};
return (
<button onClick={handleClick} className="btn-primary">
Get Started
</button>
);
}Track Form Submissions
'use client';
import { useAnalytics } from '@/lib/analytics-context';
export function ContactForm() {
const { track } = useAnalytics();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const startTime = performance.now();
// Submit form...
await submitForm();
track('form_submitted', {
form_id: 'contact',
time_to_complete: Math.round(performance.now() - startTime),
});
};
return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
</form>
);
}Custom Track Hook
// hooks/use-track-click.ts
'use client';
import { useCallback } from 'react';
import { useAnalytics } from '@/lib/analytics-context';
import type { EventProperties } from '@priv/sdk';
export function useTrackClick(eventName: string, baseProperties?: EventProperties) {
const { track } = useAnalytics();
return useCallback(
(additionalProperties?: EventProperties) => {
track(eventName, { ...baseProperties, ...additionalProperties });
},
[track, eventName, baseProperties]
);
}
// Usage
function CTAButton() {
const trackClick = useTrackClick('cta_click', { location: 'header' });
return (
<button onClick={() => trackClick({ variant: 'primary' })}>
Click Me
</button>
);
}User Identification
On Login
'use client';
import { useAnalytics } from '@/lib/analytics-context';
export function LoginForm() {
const { identify, track } = useAnalytics();
const handleLogin = async (email: string, password: string) => {
const user = await authService.login(email, password);
// Identify the user
identify(user.id, {
email: user.email,
name: user.name,
plan: user.subscription?.plan,
created_at: user.createdAt,
});
// Track the login
track('user_logged_in', {
method: 'email',
});
};
return <form onSubmit={handleLogin}>{/* ... */}</form>;
}On Logout
'use client';
import { useAnalytics } from '@/lib/analytics-context';
export function LogoutButton() {
const { track, reset } = useAnalytics();
const handleLogout = async () => {
track('user_logged_out');
await authService.logout();
// Reset analytics identity
reset();
};
return <button onClick={handleLogout}>Log Out</button>;
}Server Components Note
The PRIV SDK is client-side only. For Next.js App Router, ensure you use the 'use client' directive on any component that uses the SDK.
// This will NOT work in a Server Component
import { priv } from '@priv/sdk'; // Error!
// Correct: Mark as client component
'use client';
import { priv } from '@priv/sdk'; // Works!If you need to track server-side events (API routes, server actions), use the API directly:
// app/api/webhook/route.ts
export async function POST(request: Request) {
// Use the REST API for server-side tracking
await fetch('https://api.priv.io/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.PRIV_SECRET_KEY!,
},
body: JSON.stringify({
type: 'track',
event: 'webhook_received',
properties: { source: 'stripe' },
}),
});
}TypeScript Support
Full type safety with the analytics context:
import type { EventProperties, UserTraits } from '@priv/sdk';
interface PurchaseProperties extends EventProperties {
product_id: string;
price: number;
currency: 'USD' | 'EUR' | 'GBP';
}
function usePurchaseTracking() {
const { track } = useAnalytics();
return (properties: PurchaseProperties) => {
track('purchase', properties);
};
}Performance Tips
- Initialize once - Put the provider at the root of your app
- Memoize callbacks - Use
useCallbackfor event handlers - Batch events - The SDK batches automatically, no need to debounce
- Use Suspense - Wrap PageViewTracker in Suspense to avoid hydration issues
// Optimal setup
<AnalyticsProvider apiKey={apiKey}>
<Suspense fallback={null}>
<PageViewTracker />
</Suspense>
{children}
</AnalyticsProvider>Next Steps
- Configuration - Configure batching and auto-tracking
- API Reference - Full method documentation