user-journey-analytics
v1.0.5
Published
A lightweight user journey tracker for React.js and Next.js applications. Track page flows, user actions, and time spent with optional backend integration. Works with any backend and any database - no analytics tools, cookies, or accounts required.
Maintainers
Readme
user-journey-analytics
A lightweight user journey tracker for frontend applications. tracks page flows, user actions, and time spent with full analytics calculation. Optionally send data to your backend - no analytics tools, cookies, or accounts required.
What It Does
This package calculates and tracks:
- Page Navigation - Every route change is tracked
- User Actions - Track button clicks, form submissions, and custom events
- Time Spent - Calculation of time spent on each page
- Session Data - Complete journey data with timestamps and metadata
- Export Analytics - Export full journey data as JSON for analysis
All analytics are calculated and stored in the browser. Backend integration is completely optional — you can use it purely for client-side analytics or optionally send data to your own backend.
Why user-journey-analytics?
Most analytics tools are:
- Heavy and bloated
- Paid subscriptions required
- Backend-dependent
- Overkill for dashboards, POS systems, and MVPs
This package is built for developers, QA teams, and product managers who want:
- Simple setup - One provider, zero configuration
- Full control - Your data stays in your app
- Debug-friendly - Export and analyze journey data easily
- Privacy-first - No cookies, no external tracking
- Lightweight - Minimal bundle size impact
- Optional backend - Use client-side only or send to your backend
Features
Core Analytics Features
- Automatic page tracking - Works with App Router & Pages Router
- Manual action tracking - Track button clicks, form submissions, custom events
- Time spent per page - Automatic calculation of user engagement
- Export journey data - Get full journey as JSON for analysis
- Session tracking - One journey per browser tab/window
- Persistence - Journey data survives page refreshes (localStorage)
- Dev-only mode - Automatically disables in production builds
- No cookies - Privacy-friendly, no external tracking scripts
- TypeScript support - Full type definitions included
Optional Backend Integration
- Backend integration - Send events to your own API (any backend, any database)
- Event batching - Automatic batching for efficient data transmission
- Reliable delivery - Uses
sendBeacon()for guaranteed delivery on page unload
Installation
npm install user-journey-analyticsor
yarn add user-journey-analyticsor
pnpm add user-journey-analyticsQuick Start
Step 1: Wrap Your App with JourneyProvider
Important:
JourneyProvidercan be used in server components (no"use client"needed). TheuseJourney()hook still requires client components (React limitation).
App Router (app/layout.tsx) - Server Component
// No "use client" needed! JourneyProvider works in server components
import { JourneyProvider } from "user-journey-analytics";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<JourneyProvider appName="My App">
{children}
</JourneyProvider>
</body>
</html>
);
}Note: If you're using Next.js App Router, you can use
JourneyProviderdirectly in your server componentlayout.tsxwithout the"use client"directive. The provider automatically handles client-side tracking internally.
Pages Router (pages/_app.tsx)
import type { AppProps } from "next/app";
import { usePagesRouter } from "user-journey-analytics";
function MyApp({ Component, pageProps }: AppProps) {
usePagesRouter({
appName: "My App",
devOnly: true,
session: true // persist defaults to true, no need to specify
});
return <Component {...pageProps} />;
}
export default MyApp;Step 2: Track Actions (Optional)
"use client";
import { useJourney } from "user-journey-analytics";
export default function MyButton() {
const { trackAction } = useJourney();
return (
<button onClick={() => trackAction("Button: Get Started Clicked")}>
Get Started
</button>
);
}That's it! Page navigation is automatically tracked and analytics are calculated.
Usage Examples
Track Button Clicks
"use client";
import { useJourney } from "user-journey-analytics";
export default function ProductCard() {
const { trackAction } = useJourney();
return (
<button
onClick={() => trackAction("Product: Add to Cart - Product A")}
>
Add to Cart
</button>
);
}Track Form Interactions
"use client";
import { useState } from "react";
import { useJourney } from "user-journey-analytics";
export default function ContactForm() {
const { trackAction } = useJourney();
const [email, setEmail] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
trackAction("Form: Contact Submitted");
// Submit form...
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => {
setEmail(e.target.value);
trackAction("Form: Email Field Focused");
}}
/>
<button type="submit">Submit</button>
</form>
);
}Track Actions with Metadata
Metadata is sent to your backend API (if configured) but is not included in the exported JourneyData format. Use metadata for rich analytics in your backend.
"use client";
import { useJourney } from "user-journey-analytics";
export default function ProductCard({ productId, productName }) {
const { trackAction } = useJourney();
const handleAddToCart = () => {
trackAction("Product: Add to Cart", {
productId,
productName,
price: 29.99,
category: "electronics",
timestamp: Date.now(),
userAgent: navigator.userAgent
});
};
return (
<button onClick={handleAddToCart}>
Add to Cart
</button>
);
}Note: Metadata is included in events sent to your backend API, but the exportJourney() function returns the legacy format without metadata. To access metadata, query your backend database.
Export Journey Data
"use client";
import { useJourney } from "user-journey-analytics";
export default function ExportButton() {
const { exportJourney } = useJourney();
const handleExport = () => {
const data = exportJourney();
// Download as JSON file
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `journey-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
};
return <button onClick={handleExport}>Export Journey</button>;
}Export to CSV
You can convert the exported journey data to CSV format and download it as a .csv file for analysis in spreadsheet applications. Note that metadata is not included in the exported data - it's only available via your backend database.
See the demo application for complete CSV export implementation examples.
Clear Journey Data
"use client";
import { useJourney } from "user-journey-analytics";
export default function ClearButton() {
const { clearJourney } = useJourney();
const handleClear = () => {
if (confirm("Are you sure you want to clear all journey data?")) {
clearJourney();
console.log("Journey data cleared");
}
};
return <button onClick={handleClear}>Clear Journey</button>;
}Architecture
Server Component Support
The package now supports Next.js server components through a hybrid architecture:
JourneyProvider- Server component compatible wrapper that can be used in server components without"use client"JourneyProviderClient- Internal client component that handles all tracking logic (automatically rendered byJourneyProvider)useJourney()- Client-only hook (React limitation: hooks cannot be used in server components)
This design allows you to:
- Use
JourneyProviderin server component layouts without"use client"directive - Maintain backward compatibility with existing code
- Work seamlessly in both React and Next.js applications
How It Works
Server Component (layout.tsx)
└─ <JourneyProvider> (server component)
└─ <JourneyProviderClient> (client component)
├─ Tracks page navigation
├─ Handles browser APIs
└─ Manages analytics storeAPI Reference
JourneyProvider
Server Component Compatible - Can be used in server components (Next.js App Router) without "use client" directive.
Provider component that automatically tracks page navigation and calculates analytics.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| appName | string | undefined | Optional name for your application. Appears in exported journey data. |
| devOnly | boolean | false | If true, tracking only works when NODE_ENV === "development". Automatically disables in production builds. |
| persist | boolean | true | If true, saves journey data to localStorage. Data survives page refreshes and browser restarts. |
| session | boolean | false | If true, uses sessionStorage for one journey per browser tab/window. Data clears when tab is closed. |
| storageKey | string | "user-journey-analytics" | Custom key for localStorage/sessionStorage. Use this if you need multiple separate trackers. |
| endpoint | string | undefined | (Optional) Backend API endpoint to send events to (e.g., /api/journey). If provided, events are batched and sent to backend. |
| apiKey | string | undefined | (Optional) API key for backend authentication. Sent via Authorization: Bearer header or URL param. |
| flushInterval | number | 30000 | (Optional) Time in milliseconds between automatic flushes (default: 30 seconds). |
| batchSize | number | 10 | (Optional) Number of events to buffer before auto-flushing (default: 10). |
Example
<JourneyProvider
appName="My E-commerce App"
devOnly={true}
session={true}
storageKey="my-custom-key"
>
{children}
</JourneyProvider>Note:
persistdefaults totrue, so journey data is automatically saved tolocalStorageunless explicitly set tofalse.
Note: If both
persistandsessionare enabled,sessionStoragetakes precedence.
useJourney(devOnly?: boolean)
Hook that provides functions to track actions and manage journey data.
Note: This hook can only be used in client components. Add
"use client"to your component file when using this hook (this is a React limitation, not a package limitation).
Returns
An object with the following methods:
trackAction(action: string, metadata?: Record<string, unknown>): void- Track a user action with optional metadata. Metadata is sent to backend (if configured) but not included in exportedJourneyData(legacy format).exportJourney(): JourneyData- Export full journey data as JSON for analysis. Note: This returns the legacy format without metadata. Metadata is only available via backend events.clearJourney(): void- Reset all journey dataflush(): Promise<void>- (Optional) Manually flush pending events to backend (only works ifendpointis configured)
Parameters
devOnly(optional): Iftrue, only tracks whenNODE_ENV === "development"
Example
"use client";
import { useJourney } from "user-journey-analytics";
export default function MyComponent() {
const { trackAction, exportJourney, clearJourney } = useJourney();
const handleClick = () => {
trackAction("Button: Submit Form Clicked");
};
const handleExport = () => {
const data = exportJourney();
console.log(data);
// Export to file, send to server, etc.
};
return (
<div>
<button onClick={handleClick}>Submit</button>
<button onClick={handleExport}>Export Journey</button>
<button onClick={clearJourney}>Clear Data</button>
</div>
);
}usePagesRouter(options)
Hook for Pages Router integration (alternative to JourneyProvider).
Options
Same as JourneyProvider props:
appName?: stringdevOnly?: booleanpersist?: boolean- Save to localStorage (default:true)session?: boolean- Use sessionStoragestorageKey?: string- Custom storage keyendpoint?: string- (Optional) Backend API endpointapiKey?: string- (Optional) API key for authenticationflushInterval?: number- (Optional) Flush interval in millisecondsbatchSize?: number- (Optional) Batch size for events
Example
import { usePagesRouter } from "user-journey-analytics";
function MyApp({ Component, pageProps }) {
usePagesRouter({
appName: "My App",
devOnly: true,
session: true // persist defaults to true
});
return <Component {...pageProps} />;
}JourneyData Type
The exportJourney() function returns a JourneyData object with the following structure:
interface JourneyData {
appName?: string; // App name if provided
sessionStart: number; // Timestamp when session started
pages: string[]; // Array of visited page paths
actions: JourneyAction[]; // Array of tracked actions
pageVisits: PageVisit[]; // Array of page visits with timing
timestamps: Record<string, number>; // Object mapping pages to timestamps
}
interface JourneyAction {
page: string; // Page where action occurred
action: string; // Action description
time: string; // ISO timestamp
// Note: metadata is sent to backend but not included in exported JourneyData (legacy format)
}
interface PageVisit {
path: string; // Page path
timestamp: number; // Visit timestamp
timeSpent?: number; // Time spent in milliseconds
}Example Output
{
"appName": "My App",
"sessionStart": 1705572191000,
"pages": ["/", "/about", "/products", "/checkout"],
"actions": [
{
"page": "/products",
"action": "Button: Add to Cart Clicked",
"time": "2025-01-18T10:23:11.000Z"
},
{
"page": "/checkout",
"action": "Form: Payment Submitted",
"time": "2025-01-18T10:25:30.000Z"
}
],
"pageVisits": [
{
"path": "/",
"timestamp": 1705572191000,
"timeSpent": 2500
},
{
"path": "/about",
"timestamp": 1705572193500,
"timeSpent": 12000
},
{
"path": "/products",
"timestamp": 1705572205500,
"timeSpent": 45000
},
{
"path": "/checkout",
"timestamp": 1705572250500
}
],
"timestamps": {
"/": 1705572191000,
"/about": 1705572193500,
"/products": 1705572205500,
"/checkout": 1705572250500
}
}Configuration Options
Dev-Only Mode
Automatically disable tracking in production:
<JourneyProvider devOnly={true}>
{children}
</JourneyProvider>When devOnly={true}, tracking only works when NODE_ENV === "development". Perfect for development and QA environments.
Persistence (localStorage)
Journey data is saved to localStorage by default and automatically restored when the page reloads. This allows journey data to survive page refreshes and browser restarts.
To disable persistence:
<JourneyProvider persist={false}>
{children}
</JourneyProvider>Session Tracking (sessionStorage)
Track one journey per browser session:
<JourneyProvider session={true}>
{children}
</JourneyProvider>Journey data survives page refreshes but is automatically cleared when the browser tab/window is closed.
Custom Storage Key
Use a custom key for multiple trackers:
<JourneyProvider storageKey="my-custom-tracker-key">
{children}
</JourneyProvider>Use Cases
- POS Systems - Track cashier workflows and identify bottlenecks
- Admin Dashboards - Monitor admin user journeys and feature usage
- QA Testing - Reproduce bugs with complete user journey data
- User Flow Debugging - Understand how users navigate your app
- MVP Analytics - Get insights without third-party analytics tools
- Internal Tools - Track usage of internal applications
- A/B Testing - Compare user journeys across different variants
Important Notes
Server Component Support:
JourneyProvidercan be used in server components (Next.js App Router) without the"use client"directive- The
useJourney()hook still requires client components because React hooks can only be used in client components (this is a React limitation, not a package limitation) - Works seamlessly in both React and Next.js applications
- Backward compatible: existing code using
"use client"withJourneyProvidercontinues to work
Analytics First: All analytics are calculated and stored in the browser. Backend integration is completely optional.
Metadata Handling:
- Metadata passed to
trackAction()is sent to your backend API (if configured) - Metadata is not included in the exported
JourneyData(legacy format) - To access metadata, query your backend database where events are stored
- Metadata passed to
Flexible Usage: Works great for development, QA, internal tools, and production applications. Use client-side only or optionally send to your backend.
Storage Limitations:
localStoragehas ~5-10MB limitsessionStoragehas ~5-10MB limit- Large journeys may hit storage limits
Browser Support: Requires browsers that support:
localStorage/sessionStorage- ES6+ features
- Modern JavaScript APIs
navigator.sendBeacon()(for backend integration)
Troubleshooting
Tracking not working?
- Verify JourneyProvider is set up: Make sure it wraps your app in
layout.tsx(works in both server and client components) - Check useJourney hook usage: If using
useJourney()hook, ensure"use client"is at the top of your component file (hooks require client components) - Check devOnly mode: If
devOnly={true}, ensureNODE_ENV === "development" - Browser console: Check for any warnings or errors
Data not persisting?
- Check persist prop:
persistdefaults totrue. If you've setpersist={false}, persistence is disabled - Check browser storage: Verify localStorage is available and not disabled
- Storage quota: Check if storage quota is exceeded
TypeScript errors?
- Install types: Types are included, no additional
@typespackage needed - Check imports: Ensure you're importing from
"user-journey-analytics"
Optional: Backend API Integration
Note: Backend integration is completely optional. The package works perfectly fine without it — all analytics are calculated and stored in the browser. You can export and analyze data without any backend.
For production use, you can optionally send events to your own backend API. This follows the same architecture as GA, Segment, and PostHog SDKs.
Architecture Overview
Browser
├─ Analytics calculation (always happens)
├─ in-memory store
├─ localStorage (temporary buffer)
└─ sendBeacon / fetch (optional)
↓
Your Backend API (any language, any framework)
↓
Your Database (Postgres, MongoDB, MySQL, SQLite, ClickHouse, etc.)Key Principles:
- Analytics are always calculated in the browser
- Client buffers events and sends in batches (if endpoint provided)
- Uses
navigator.sendBeacon()for reliable delivery - localStorage acts as safety buffer, not source of truth
- Backend is optional — analytics work without it
- Backend-agnostic - Works with any backend (Node.js, Python, Go, PHP, etc.)
- Database-agnostic - Works with any database (SQL, NoSQL, etc.)
Quick Setup
1. Configure JourneyProvider with Backend
<JourneyProvider
appName="My App"
endpoint="/api/journey" // Your backend API endpoint
apiKey="your-api-key" // Optional: API key for auth
flushInterval={30000} // Flush every 30 seconds
batchSize={10} // Flush after 10 events
>
{children}
</JourneyProvider>2. Create Backend API Endpoint
Your backend should accept POST requests with this structure:
POST /api/journey
Content-Type: application/json
Authorization: Bearer your-api-key // Optional
{
"sessionId": "session-1234567890-abc123",
"userId": "optional-user-id",
"appName": "My App",
"events": [
{
"type": "page_view",
"path": "/home",
"timestamp": 1705572191000
},
{
"type": "action",
"path": "/home",
"action": "Button: Get Started Clicked",
"timestamp": 1705572192000,
"metadata": {
"buttonId": "get-started",
"buttonType": "primary",
"userAgent": "Mozilla/5.0..."
}
},
{
"type": "page_exit",
"path": "/home",
"timestamp": 1705572195000,
"timeSpent": 4000
}
]
}Response: Return 200 OK on success.
3. Event Types
type EventType = "page_view" | "action" | "page_exit";
interface JourneyEvent {
type: EventType;
path?: string; // For page_view and page_exit
action?: string; // For action events
timestamp: number; // Unix timestamp in milliseconds
timeSpent?: number; // For page_exit events (milliseconds)
metadata?: Record<string, unknown>; // Optional custom data
}Features
Automatic Batching
- Events are buffered in memory
- Sent in batches (configurable
batchSize) - Reduces server load and improves performance
Smart Flushing
- Time-based: Flushes every N milliseconds (
flushInterval) - Count-based: Flushes after N events (
batchSize) - Visibility change: Flushes when page becomes hidden
- Page unload: Flushes on
beforeunloadandpagehide
Reliable Delivery
- Uses
navigator.sendBeacon()(works on tab close, refresh, route change) - Falls back to
fetch()withkeepalive: true - Retry queue for failed batches
- Buffer cleared only after successful send (200 OK)
Manual Flush
"use client";
import { useJourney } from "user-journey-analytics";
export default function FlushButton() {
const { flush } = useJourney();
const handleFlush = async () => {
try {
await flush();
console.log("Events flushed successfully");
} catch (error) {
console.error("Failed to flush events:", error);
}
};
return <button onClick={handleFlush}>Flush Events</button>;
}Package Information
- Package Name:
user-journey-analytics - Version:
1.04 - License: MIT
- Node Version: >=16.0.0
- Peer Dependencies:
next: >=13.0.0react: >=18.0.0
License
MIT © 2025 Nimra Zahoor
Demo
A complete Next.js demo application showcasing all features:
- GitHub: https://github.com/Nimra-Zahoor/demo-user-journey
- Features:
- Automatic page tracking
- Action tracking with metadata
- Backend API integration with SQLite
- Export to JSON, CSV, and PDF
- Real-time journey viewer
- Database events viewer
- Complete implementation examples
Support
If you have questions or need help, please:
- Open an issue on GitHub
- Check existing issues and discussions
Made with ❤️ for the Frontend Analytics community
