Updated 2 October 2026

Analytics for Next.js

Privacy-friendly, open-source analytics that lives inside your Next.js app: one component, one route handler, and events that go through your own domain.

In short

  • npm i trckable, then one component and one route in the App Router. About 2,264 bytes gzipped added to your bundle, by CI's measurement.
  • Events go through your own domain, so there is no separate script file for a blocklist to match by name, and Safari keeps the visitor cookie for 400 days instead of 7.
  • Add Stripe, Paddle, Lemon Squeezy, Polar or Dodo, and revenue shows up per source and page.

Install#

npm i trckable

The package is MIT licensed and has no dependencies. It talks to a trckable server: your own (it is free to self-host) or trckable Cloud, where the host is https://cloud.trckable.com.

Add the component once, in the root layout:

// app/layout.tsx
import { Analytics } from 'trckable/next'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Analytics site="tkb_a1b2c3d4" />
      </body>
    </html>
  )
}

Then add the route that carries events to your server:

// app/api/e/route.ts
export { POST } from 'trckable/next'

Set two environment variables. Your server's address, and the site's proxy key, which you find in Settings → General → For developers. The proxy key stays on your server, never in the browser.

# .env
TRCKABLE_HOST=https://stats.example.com
TRCKABLE_PROXY_KEY=your-proxy-key

That is the whole install. Route changes in the App Router are counted as page views automatically, once each.

Prefer a command? npx trckable init looks at the project and prints exactly what it would change, and writes only with --yes. npx trckable doctor reads only: it checks the server, the script, the proxy key and that visitors are seen at their own address.

Why through your own domain#

A script loaded from an analytics host is something ad blockers recognise by name. An event sent to /api/e on your own domain, from code that is part of your own bundle, looks like your app talking to itself, because that is what it is.

That is also why this is the setup we call the accurate one, not the unblockable one. Nothing is unblockable, and a determined blocker can still block anything. What changes is that there is no analytics file to match.

Three more things follow from the route being yours:

  • Safari keeps returning visitors. A cookie written by JavaScript is capped at seven days in Safari. A cookie set by your own server route lasts 400 days, so a visitor who comes back after two weeks is still the same visitor.
  • Do Not Track and Global Privacy Control are passed on, so a site that honours them still does.
  • Visitors are seen at their own address and country, because the route forwards it with the proxy key.

The key matters. Without it the route answers with an error that says what to set, rather than quietly counting every visitor as your server. npx trckable doctor --proxy checks it.

What you get#

  • Page views, goals and engagement: SPA navigations, outbound links, downloads, scroll goals and time on page.
  • Events that survive a bad moment. If an event cannot be sent, it is kept in the browser and sent on a later page. The server counts each one once.
  • Counted once, however many copies load. If the tag and the package both end up on a page, the second does nothing.
  • A query that changes on the same page is not a new page. A search box that keeps its text in the address bar counts as one page.
  • Leave out your own visits by opening your site once with ?trckable=ignore.

For goals, a call or no code at all:

import { track } from 'trckable'

track('signup', { plan: 'pro' })
<button data-trckable-goal="signup" data-trckable-goal-plan="pro">Start trial</button>

Selling something? Add revenue#

If you charge through Stripe, Paddle, Lemon Squeezy, Polar or Dodo Payments, connect the provider with one key in Settings → Payments. Each payment is then credited to the visit that earned it, so revenue shows next to traffic: per source, per page, per campaign.

When your server creates the checkout, pass the visitor along with one helper:

import { getIds, checkoutFields } from 'trckable/server'
import { cookies } from 'next/headers'

const ids = getIds(await cookies())
await stripe.checkout.sessions.create({ ...params, ...checkoutFields('stripe', ids, 'subscription') })

Plain checkout links need no code: the tracker adds the visitor when the link is clicked. See revenue attribution and the page for your provider, such as Stripe.

No banner, if you choose#

Pass cookieless and nothing is stored in the browser at all: no cookie, no local storage. Visitors are counted with a hash that changes every day. The trade-off is that a visitor is new every day, and revenue can only be credited to a visit from the same day. In the default mode, trckable sets one first-party cookie, and in the EU and the UK that normally needs consent first.

What your own situation requires is for you and your counsel to decide. The cookieless analytics guide explains the options without pretending to be legal advice.

How big is it#

CI measures the size and fails the build when it grows. The figures below are CI's, for 1 Oct 2026.

WhatSize, gzipped
trckable/react added to your app2,264 B (CI fails above 2,560)
The script tag, pageviews only1,596 B
The script tag with goals, outbound links and checkout attribution2,045 B

How each number is measured is on the benchmarks page.

Questions#

Does it work with the Pages Router or plain React?
The component and route above are written for the App Router. For plain React apps (Vite, React Router, Remix), use trckable/react, which takes the site id and your server's address. Anything else can call init({ site, host }) once on the client, and the script tag works anywhere.
Do I have to self-host?
No. Point TRCKABLE_HOST at your own server, or at https://cloud.trckable.com if you let us run it. The program is the same, and self-hosting is free.
Does it slow my site down?
The tracker adds about 2 KB gzipped to your bundle (the table above has CI's figure) and sends its events in the background with keepalive. In the proxied setup the browser makes no request to a separate analytics host.
How is this different from Vercel Analytics?
trckable is open source, you can host it yourself, it is not tied to a hosting platform, and it credits payments to the visits behind them. For what Vercel's analytics offers, see Vercel's own documentation.

Keep reading#

Get started