@riuve/next
v1.0.4
Published
Behavioral event tracking SDK for Next.js
Maintainers
Readme
@riuve/next
Behavioral event tracking SDK for Next.js — automatic page tracking, offline queuing, and intelligent batching.
Table of Contents
- What is Riuve?
- Installation
- Quick Start
- Usage with React Hooks
- Auto Page Tracking
- User Identification
- Super Properties
- Advanced Configuration
- Best Practices
- API Reference
What is Riuve?
Riuve is a behavioral push notification platform. You track user events via the SDK, and from the Riuve Dashboard you create campaigns that trigger push notifications based on those behaviors.
Example: A user adds an item to their cart but doesn't check out → 2 hours later, send them a reminder notification.
Features
- ✅ Auto Page Tracking —
$page_viewevents on every route change (App Router & Pages Router) - ✅ Offline Queue — Events are persisted in
localStorageand sent when back online - ✅ Intelligent Batching — Events are grouped and sent in batches to minimize API calls
- ✅ Auto Retry — Failed requests retry with exponential backoff (3 attempts)
- ✅ Background Flush — Events flush automatically when the tab becomes hidden or the page unloads
- ✅ Session Management — Automatic
$session_start/$session_endtracking - ✅ Super Properties — Attach global properties to every event automatically
- ✅ Pre-init Queue — Events tracked before
initialize()completes are not lost - ✅ TypeScript — Full type definitions included
- ✅ SSR Safe — All browser APIs are guarded with
typeof window !== 'undefined'
Installation
npm install @riuve/next
# or
yarn add @riuve/next
# or
pnpm add @riuve/nextRequirements: Next.js 14+, React 18+
Quick Start
Step 1: Add the Provider
Wrap your app with RiuveProvider in your root layout. Get your API key from the Riuve Dashboard.
App Router (app/layout.tsx):
import { RiuveProvider } from '@riuve/next';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<RiuveProvider apiKey={process.env.NEXT_PUBLIC_RIUVE_API_KEY!}>
{children}
</RiuveProvider>
</body>
</html>
);
}Pages Router (pages/_app.tsx):
import type { AppProps } from 'next/app';
import { RiuveProvider } from '@riuve/next';
export default function App({ Component, pageProps }: AppProps) {
return (
<RiuveProvider apiKey={process.env.NEXT_PUBLIC_RIUVE_API_KEY!}>
<Component {...pageProps} />
</RiuveProvider>
);
}Step 2: Track Events
'use client';
import { useRiuve } from '@riuve/next';
export default function ProductPage({ product }) {
const { track } = useRiuve();
const handleAddToCart = async () => {
await track('add_to_cart', {
product_id: product.id,
product_name: product.name,
price: product.price,
});
};
return <button onClick={handleAddToCart}>Add to Cart</button>;
}Step 3: Enable Auto Page Tracking
Add usePageTracking() once anywhere inside RiuveProvider — it automatically fires a $page_view event on every route change.
'use client';
import { usePageTracking } from '@riuve/next';
export default function Analytics() {
usePageTracking();
return null;
}// app/layout.tsx
import { RiuveProvider } from '@riuve/next';
import Analytics from './Analytics';
export default function RootLayout({ children }) {
return (
<html>
<body>
<RiuveProvider apiKey={process.env.NEXT_PUBLIC_RIUVE_API_KEY!}>
<Analytics />
{children}
</RiuveProvider>
</body>
</html>
);
}Usage with React Hooks
useRiuve()
The main hook for tracking events and identifying users.
'use client';
import { useRiuve } from '@riuve/next';
export default function CheckoutButton() {
const { track, identify, isReady } = useRiuve();
const handleCheckout = async () => {
await track('checkout_started', {
cart_value: 149.99,
item_count: 3,
});
};
return (
<button onClick={handleCheckout} disabled={!isReady()}>
Checkout
</button>
);
}useRiuveContext()
Access the provider context directly — includes the isReady boolean as reactive state.
'use client';
import { useRiuveContext } from '@riuve/next';
export default function TrackingStatus() {
const { isReady, track } = useRiuveContext();
return (
<div>
<span>Tracking: {isReady ? 'Active' : 'Initializing...'}</span>
<button onClick={() => track('test_event')}>Send Test</button>
</div>
);
}
useRiuvevsuseRiuveContext: UseuseRiuvein most cases — it's lighter. UseuseRiuveContextwhen you need the reactiveisReadyboolean to update your UI.
Direct import (without hooks)
You can also import the Riuve singleton directly. Useful in utility functions, server actions, or outside React components.
import Riuve from '@riuve/next';
// In a utility function
export async function trackPurchase(orderId: string, amount: number) {
await Riuve.track('purchase_completed', { order_id: orderId, amount });
await Riuve.flush(); // Send immediately
}Note: Always call
Riuve.track()on the client side only. Do not call it in Server Components or API routes.
Auto Page Tracking
usePageTracking() listens to Next.js pathname changes and fires a $page_view event automatically.
usePageTracking();
// Tracks: { page_url, page_path, page_title } on every route changeWith extra properties:
usePageTracking({
extraProperties: {
app_section: 'marketing',
experiment: 'hero_v2',
},
});Event payload:
{
"name": "$page_view",
"properties": {
"page_url": "https://example.com/products/123",
"page_path": "/products/123",
"page_title": "Product Name — My Store"
}
}User Identification
Call identify() when a user logs in or when their information is available. This links all subsequent events to their user profile in the Riuve Dashboard.
'use client';
import { useRiuve } from '@riuve/next';
export default function LoginForm() {
const { identify } = useRiuve();
const handleLogin = async (user: User) => {
// Authenticate user...
await identify(user.id, {
email: user.email,
name: user.name,
plan: user.subscriptionPlan,
created_at: user.createdAt,
});
};
return <form onSubmit={handleLogin}>{/* ... */}</form>;
}When to call identify:
- After a user logs in
- After a user signs up
- On page load if the user is already authenticated
- When user profile data changes
When to call reset:
// On logout — clears identity and generates a new anonymous ID
await Riuve.reset();Super Properties
Super properties are attached to every event automatically. Set them once for properties that always apply, like plan tier, experiment group, or app version.
import Riuve from '@riuve/next';
// Set after initialization
await Riuve.registerSuperProperties({
app_version: '2.4.0',
plan: 'pro',
experiment_group: 'variant_b',
});
// Now every track() call includes these properties automatically
await Riuve.track('button_clicked', { button_id: 'upgrade' });
// → { button_id: "upgrade", app_version: "2.4.0", plan: "pro", experiment_group: "variant_b" }// Register only if not already set (useful for first-time values)
await Riuve.registerSuperPropertiesOnce({ first_referrer: document.referrer });
// Remove a single super property
await Riuve.unregisterSuperProperty('experiment_group');Advanced Configuration
<RiuveProvider
apiKey={process.env.NEXT_PUBLIC_RIUVE_API_KEY!}
disableTracking={process.env.NODE_ENV !== 'production'} // Disable in development
config={{
// Batching
batchSize: 10, // Events per batch (default: 10)
batchTimeout: 30000, // Auto-send after N ms (default: 30000)
// Network
requestTimeout: 10000, // API request timeout in ms (default: 10000)
maxRetries: 3, // Retry attempts on failure (default: 3)
retryDelay: 1000, // Base retry delay in ms (default: 1000)
// Storage
persistEvents: true, // Persist queue to localStorage (default: true)
maxQueueSize: 1000, // Max queued events (default: 1000)
eventRetentionDays: 7, // Days to keep failed events (default: 7)
// Behaviour
autoFlushOnBackground: true, // Flush on tab hide / page unload (default: true)
// Debug
debugMode: false, // Verbose console logging (default: false)
}}
>
{children}
</RiuveProvider>Recommended configs
Disable tracking entirely:
// Always disabled
<RiuveProvider apiKey={process.env.NEXT_PUBLIC_RIUVE_API_KEY!} disableTracking>
// Disabled only in development
<RiuveProvider
apiKey={process.env.NEXT_PUBLIC_RIUVE_API_KEY!}
disableTracking={process.env.NODE_ENV !== 'production'}
>Development (with tracking enabled for debugging):
{
debugMode: true,
batchSize: 1, // Send every event immediately for easier testing
batchTimeout: 0,
}Production:
{
debugMode: false,
batchSize: 10,
batchTimeout: 30000,
autoFlushOnBackground: true,
}High-traffic pages:
{
batchSize: 25,
batchTimeout: 60000,
maxQueueSize: 5000,
}Best Practices
1. Use environment variables for the API key
// .env.local
NEXT_PUBLIC_RIUVE_API_KEY=riuve_base_xxxxxxxxxxxxxxxxxxxxx<RiuveProvider apiKey={process.env.NEXT_PUBLIC_RIUVE_API_KEY!}>Never hardcode the API key directly in source files.
2. Flush after critical events
// Payment completed — send immediately, don't wait for the next batch
await Riuve.track('purchase_completed', { order_id, amount });
await Riuve.flush();3. Use consistent event naming
// ✅ Good — snake_case, verb + noun
track('product_viewed', { product_id })
track('cart_item_added', { product_id, quantity })
track('checkout_completed', { order_id, total })
// ❌ Bad — inconsistent
track('viewProduct')
track('AddToCart')
track('checkout')4. Don't block UI for tracking
// ✅ Good — fire and forget
const handleClick = () => {
track('button_clicked', { id: 'subscribe' }).catch(console.error);
router.push('/subscribe');
};
// ❌ Bad — awaiting track blocks navigation
const handleClick = async () => {
await track('button_clicked', { id: 'subscribe' }); // Don't await unless critical
router.push('/subscribe');
};5. Identify users on the client side
// ✅ Good — in a Client Component after authentication
'use client';
const { identify } = useRiuve();
useEffect(() => {
if (session?.user) identify(session.user.id, { email: session.user.email });
}, [session]);
// ❌ Bad — in a Server Component or API route
// identify() only works on the clientAPI Reference
RiuveProvider
<RiuveProvider apiKey={string} enabled?: boolean config?: RiuveConfigInput>Initializes the SDK on mount. All hooks must be used inside this provider.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| apiKey | string | — | Your Riuve API key (required) |
| disableTracking | boolean | false | When true, the SDK skips initialization and all calls become no-ops |
| config | object | — | Advanced configuration (see above) |
useRiuve()
const { track, identify, flush, isReady, getAnonymousId, getExternalUserId, registerSuperProperties } = useRiuve()| Method | Signature | Description |
|--------|-----------|-------------|
| track | (name: string, props?: object) => Promise<void> | Track an event |
| identify | (userId: string, props?: object) => Promise<void> | Identify a user |
| flush | () => Promise<void> | Send all queued events immediately |
| isReady | () => boolean | Returns true after initialization |
| getAnonymousId | () => string \| null | Returns the anonymous ID |
| getExternalUserId | () => string \| null | Returns the identified user ID |
| registerSuperProperties | (props: object) => Promise<void> | Set global event properties |
usePageTracking(options?)
usePageTracking({ extraProperties?: Record<string, any> })Tracks a $page_view event on every pathname change. Add it once in your root layout.
useRiuveContext()
const { isReady, track, identify, flush } = useRiuveContext()Same as useRiuve() but isReady is a reactive boolean state that triggers re-renders.
Riuve (singleton)
Importable anywhere on the client side.
import Riuve from '@riuve/next';| Method | Description |
|--------|-------------|
| Riuve.initialize(apiKey, config?) | Initialize the SDK manually (not needed if using RiuveProvider) |
| Riuve.track(name, props?) | Track an event |
| Riuve.identify(userId, props?) | Identify a user |
| Riuve.flush() | Send all queued events immediately |
| Riuve.reset() | Clear identity and generate a new anonymous ID |
| Riuve.setLocale(locale) | Override session locale (e.g. 'tr', 'en') |
| Riuve.registerSuperProperties(props) | Set persistent global event properties |
| Riuve.registerSuperPropertiesOnce(props) | Set only if not already set |
| Riuve.unregisterSuperProperty(key) | Remove a single super property |
| Riuve.isReady() | Returns true after initialization |
| Riuve.getAnonymousId() | Returns the anonymous ID |
| Riuve.getExternalUserId() | Returns the identified user ID |
| Riuve.getSessionId() | Returns the current session ID |
| Riuve.getQueueSize() | Returns number of queued events |
| Riuve.getConfig() | Returns a copy of the current config |
| Riuve.cleanup() | Flush and tear down (for testing / unmounting) |
Automatic Events
The SDK automatically tracks the following events — no setup required:
| Event | When | Properties |
|-------|------|------------|
| $session_start | New session begins | session_id |
| $session_end | Session ends (tab closes / 30 min idle) | session_id, session_duration_ms |
| $page_view | Route changes (requires usePageTracking) | page_url, page_path, page_title |
Support
- 📚 Documentation: docs.riuve.com
- 📧 Email: [email protected]
- 🐛 Issues: github.com/riuve/sdk
License
MIT © Riuve
