nextrack
v0.1.4
Published
Deep user activity tracking for Next.js — everything Google Analytics doesn't tell you
Downloads
497
Maintainers
Readme
nextrack
Deep user activity tracking for Next.js — the things Google Analytics doesn't tell you.
Rage clicks · Dead clicks · Scroll depth · Attention time · Form abandonment · Tab switches · Copy events · Web Vitals · JS Errors · Custom events — all in one package, stored in your own Supabase database.
Features
| Feature | Included | |---|:---:| | Page views | ✅ | | Custom events | ✅ | | Rage clicks (frustrated users) | ✅ | | Dead clicks (broken UI) | ✅ | | Click heatmaps | ✅ | | Real engaged time (not idle time) | ✅ | | Tab switch count | ✅ | | Form field abandonment | ✅ | | Copy events | ✅ | | JS error tracking | ✅ | | Web Vitals (LCP/FCP/CLS/INP/TTFB) | ✅ | | Your own database | ✅ | | No cookie banner needed (no PII) | ✅ |
Installation
npm install nextrack
# or
pnpm add nextrack
# or
yarn add nextrackPeer dependencies:
npm install @supabase/supabase-jsPrerequisites
Run the database migration in your Supabase project → SQL Editor:
-- copy contents from:
-- https://github.com/mustafaag/nextrack/blob/main/supabase/migrations/001_create_tracker_tables.sqlQuick start
1. Add the provider to your root layout
// app/layout.tsx
import { TrackerProviderWrapper } from '@/components/TrackerProviderWrapper'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<TrackerProviderWrapper>{children}</TrackerProviderWrapper>
</body>
</html>
)
}// components/TrackerProviderWrapper.tsx
'use client'
import { usePathname } from 'next/navigation'
import { TrackerProvider } from 'nextrack/react'
import { SupabaseAdapter } from 'nextrack/adapters/supabase'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
const adapter = new SupabaseAdapter({ client: supabase })
export function TrackerProviderWrapper({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
return (
<TrackerProvider
appId="my-app"
adapter={adapter}
pathname={pathname}
trackClicks
trackScroll
trackForms
trackErrors
trackWebVitals
trackEngagement
trackCopy
debug={process.env.NODE_ENV === 'development'}
>
{children}
</TrackerProvider>
)
}2. Track custom events
'use client'
import { useTracking } from 'nextrack/react'
export function CTAButton() {
const { track } = useTracking()
return (
<button onClick={() => track('cta_clicked', { variant: 'hero', plan: 'pro' })}>
Get started
</button>
)
}Or use the wrapper component:
import { TrackClick } from 'nextrack/react'
<TrackClick event="upgrade_clicked" properties={{ from: 'pricing_table' }}>
<button>Upgrade</button>
</TrackClick>3. Identify logged-in users
const { identify } = useTracking()
// After login:
identify(user.id, { email: user.email, plan: user.plan })HTTP adapter (optional)
If you prefer to route events through your own API instead of writing directly to Supabase from the client:
import { HttpAdapter } from 'nextrack/adapters/http'
const adapter = new HttpAdapter({
endpoint: '/api/nextrack',
headers: { 'x-nextrack-secret': process.env.NEXTRACK_SECRET! },
})Add the route handler:
// app/api/nextrack/[...path]/route.ts
import { createNextHandler } from 'nextrack/next'
import { SupabaseAdapter } from 'nextrack/adapters/supabase'
import { createClient } from '@supabase/supabase-js'
const adapter = new SupabaseAdapter({
client: createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
),
})
const { POST, PATCH } = createNextHandler({
adapter,
secret: process.env.NEXTRACK_SECRET,
})
export { POST, PATCH }Configuration options
| Option | Default | Description |
|---|---|---|
| appId | required | Unique app identifier |
| adapter | required | Storage adapter (Supabase or HTTP) |
| sampleRate | 1 | Fraction of sessions to track (0–1) |
| batchSize | 10 | Events per batch flush |
| flushInterval | 5000 | Flush interval in ms |
| trackClicks | true | Track all clicks |
| trackRageDeadClicks | true | Detect rage & dead clicks |
| trackScroll | true | Scroll depth tracking |
| trackForms | true | Form start/submit/abandon |
| trackFormFields | false | Field-level focus/blur (privacy-sensitive) |
| trackErrors | true | Uncaught JS errors |
| trackWebVitals | true | LCP, FCP, CLS, TTFB, INP |
| trackEngagement | true | Real attention time, tab switches |
| trackCopy | true | Copy events (text length only, no content) |
| idleThresholdMs | 30000 | Inactivity threshold for engagement |
| rageClickCount | 3 | Clicks to trigger rage click |
| rageClickWindowMs | 500 | Time window for rage click detection |
| maskTextContent | false | Strip text from click selectors |
| excludePaths | [] | Paths to skip (supports * wildcard) |
| debug | false | Log events to console |
Privacy
- No cookies — uses
localStoragefor anonymous ID,sessionStoragefor session ID - No PII collected by default — user identification is opt-in via
identify() - Copy events only record text length, never content
- Click selector scrubbing via
maskTextContent: true - RLS can be enabled on tracker tables in Supabase
Self-hosted dashboard
A full analytics dashboard is available at github.com/yourusername/nextrack.
Pages: Overview · Sessions · Events · Heatmaps · Web Vitals · Forms
License
MIT
