Simplytics

Add analytics to a Next.js App Router site without cookies

To add analytics to a Next.js App Router app, put one script tag in the root layout using the next/script component. For Simplytics that is a <Script> in app/layout.tsx with strategy="afterInteractive". It sets no visitor cookies and counts client-side navigations automatically, without a usePathname() effect.

After the first load, every <Link> click is a soft navigation: Next.js swaps the route with the browser History API instead of reloading, so a tracker that only fires on page load misses it. This tag hooks the same API.

Where the tag goes in the App Router

Put it once in the root layout, app/layout.tsx. The App Router doesn't re-mount the root layout between routes, so the tag loads once and stays live.

// app/layout.tsx
import Script from 'next/script'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          async
          src="https://simplytics.dev/track.js"
          data-key="YOUR_KEY"
          strategy="afterInteractive"
        />
      </body>
    </html>
  )
}

Use next/script, not a bare <script>: Next.js recommends it for third-party scripts, and its strategy prop controls when the tag loads, and Next.js lists analytics under afterInteractive — early enough to catch the first view, late enough not to slow hydration. Replace YOUR_KEY with your site's public key from the dashboard; it's safe to commit.

The script is ~1.2 KB gzipped; GA4's tag weighs about 140 KB (see how much JavaScript each analytics tool ships).

On the Pages Router (a pages/ directory), put the tag in pages/_document.tsx, inside <Head>.

Do client-side navigations get counted?

Yes — with Simplytics, App Router soft navigations are counted automatically, and you should not add a usePathname() effect to send page views. A <Link> click or router.push() calls history.pushState, and the back button fires popstate.

The Simplytics script wraps pushState and replaceState and listens for popstate, so each new path records a view; the page you left counts as the referrer and folds into Direct. Query-only and hash-only changes (a filter, a tab, an anchor) are ignored.

The App Router has no route-change event (router.events is gone). The documented workaround is to fire a page view from usePathname() and useSearchParams() in a Client Component. With this tag, that code double-counts every navigation:

What each tool needs:

Tool App Router soft navigation What you add
Simplytics Automatic Nothing beyond the tag. It patches the History API the App Router uses.
Plausible Automatic Nothing for History-API routers; its standard script handles pushState navigation.
Fathom Manual (their Next.js doc) Fathom's Next.js guide fires views from a usePathname() effect via fathom-client; its generic SPA mode is the data-spa="auto" attribute.
Google Analytics 4 Config or manual Turn on the "Page changes based on browser history events" Enhanced-Measurement option, or send page_view from a usePathname() effect (Next.js ships a GoogleAnalytics component in @next/third-parties) — never both, or you double-count.

For the general version, see how to track single-page app page views without cookies; for Astro, add analytics to an Astro site.

How to verify the first hit

Test a production build, not next dev:

  1. Run next build && next start (or deploy).
  2. Open DevTools Network, filter for track, and load a page. You should see a POST to https://simplytics.dev/track that returns {"status":"success"}.
  3. Click an internal <Link>. A new POST /track should fire for the new path. If it doesn't, the tag isn't in the root app/layout.tsx, a bare <script> was used instead of next/script, or an extension is blocking it.
  4. Open your dashboard. For a new account, its first accepted page view starts the 30-day free trial (no card). Adding another site does not restart that account trial. The site shows as verified once a valid hit arrives.

How to exclude your own visits

Three ways to keep your own traffic out:

When another tool fits better

Plausible auto-tracks App Router navigation the same way. What differs is no visitor cookies, a primary analytics database in Warsaw, raw rows deleted nightly with no scheduled expiry for held daily totals, and the price: $1/month, billed yearly ($12/year, with unlimited sites and pageviews, and a 30-day trial with no card) against Plausible's $9/month. Compare Simplytics vs Plausible and vs Google Analytics, try the demo or see pricing.

See also: add analytics to WordPress without a plugin, the no-framework version of the same one-line tag.

Next.js behaviour reflects the App Router docs (next/script, root layout, usePathname) and each vendor's published Next.js or SPA guidance. Last verified: 2026-09-13.

← All posts