@jillen/analytics
v5.0.0
Published
Lightweight human-visit and Web Vitals analytics for React apps. Framework-agnostic core with a Next.js adapter.
Maintainers
Readme
@jillen/analytics
Lightweight human-visit and Web Vitals analytics for React apps. Framework-agnostic core with a Next.js adapter — works with Next.js, Vite, React Router, TanStack Router, Astro, and any other React-based setup.
Sends events to https://analytics.jillen.com.
What it tracks
- Page views on initial mount and on every route change.
- Sessions with a 30-minute idle timeout. Heartbeats fire at 15s → 60s → 5m → 15m intervals while the user is active; pause when the tab is hidden.
- Web Vitals: Largest Contentful Paint, Cumulative Layout Shift, Interaction to Next Paint, plus navigation-timing breakdown and resource analysis.
- Visitor identity: localStorage-based fingerprint for anonymous users; deterministic id when you pass
username.
Tracking only fires when process.env.NODE_ENV === "production". No requests in dev.
JS-running bots (Puppeteer, Playwright, headless Chromium) are filtered out client-side via isbot; their events are not sent.
Installation
bun add @jillen/analytics
# or
npm install @jillen/analyticsUsage
The component returns null (renders no DOM). Mount it once near the root of your app — re-renders on route changes drive the page-view tracking.
Vite + React Router
// src/components/analytics-provider.tsx
import { VisitorTracker } from "@jillen/analytics"
import { useLocation } from "react-router-dom"
export function AnalyticsProvider() {
const { pathname } = useLocation()
return <VisitorTracker pathname={pathname} />
}// src/App.tsx
import { Routes, Route } from "react-router-dom"
import { AnalyticsProvider } from "@/components/analytics-provider"
export function App() {
return (
<>
<AnalyticsProvider />
<Routes>{/* ... */}</Routes>
</>
)
}Next.js (App Router)
Import from the /next sub-path — it wires up usePathname() for you:
// app/lib/analytics-provider.tsx
"use client"
import { VisitorTracker } from "@jillen/analytics/next"
export function AnalyticsProvider() {
return <VisitorTracker />
}// app/layout.tsx
import { AnalyticsProvider } from "./lib/analytics-provider"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AnalyticsProvider />
{children}
</body>
</html>
)
}TanStack Router
import { VisitorTracker } from "@jillen/analytics"
import { useRouterState } from "@tanstack/react-router"
export function AnalyticsProvider() {
const pathname = useRouterState({ select: (s) => s.location.pathname })
return <VisitorTracker pathname={pathname} />
}Identifying users
Pass a username to associate visits with an authenticated user. The id is normalised (lowercased, alphanumeric-with-dashes) and persists in localStorage. Read the user info from your auth library and forward whatever string you want to use as the visitor id:
// Clerk
const { user } = useUser()
const username = user?.username ?? user?.id ?? null
// NextAuth
const { data: session } = useSession()
const username = session?.user?.name ?? session?.user?.email ?? null
// Supabase
const user = useUser()
const username = user?.email ?? user?.id ?? null
return <VisitorTracker username={username} pathname={pathname} />null (or omitted) = anonymous visitor, fingerprinted via locally-cached browser characteristics.
API
Default entry — @jillen/analytics
interface VisitorTrackerProps {
username?: string | null
pathname: string
}
declare function VisitorTracker(props: VisitorTrackerProps): nullNext.js adapter — @jillen/analytics/next
Same component, with pathname wired from usePathname():
interface VisitorTrackerProps {
username?: string | null
}
declare function VisitorTracker(props: VisitorTrackerProps): nullMigrating from v4
See CHANGELOG.md. Short version:
- Next.js consumers: change
from "@jillen/analytics"→from "@jillen/analytics/next". Remove anysetupAnalyticsMiddlewarecalls (the/serverentry no longer exists). - Other React apps: import from default and pass
pathnamefrom your router. - Bot tracking is removed in v5. Bot data was noisy in practice. Client-side bot filtering still works — JS-running bots' events still get suppressed before reaching the network.
License
MIT
