Conclick
HomeFeaturesPricingReviewsAboutContact
Resources
Explore the knowledge base
BlogInsights, product notes, and updatesGuidesStep-by-step walkthroughs and playbooksCompareFeature and alternative comparisonsToolsFree utilities and templates
Login
Home/Guides/Add Analytics To Nextjs
Guide

How to Add Analytics to a Next.js Site

Where the tracking snippet goes in the App Router and Pages Router, script tag vs next/script, and how to fix the SPA route-change gotcha that breaks pageviews.

Analytics·8 min read
D
Deepak Yadav · Founder, Conclick
Updated July 22, 2026
nextjs.
On this page
  • The short version
  • A plain script tag works, but next/script is better
  • App Router: the snippet goes in your root layout
  • Pages Router: same job, different file
  • The gotcha nobody warns you about: route changes
  • Which tool is honest for a small Next.js site

The short version

  • Add analytics to a Next.js site by loading the tracker once in your root layout with next/script (strategy afterInteractive), or with @next/third-parties for Google Analytics.
  • The gotcha: client-side route changes never reload the page, so a naive snippet counts only the first pageview.
  • Umami-based trackers handle that automatically.
  • A raw Google Analytics snippet does not.

I have shipped this on more Next.js apps than I can count, and the same two things break every time. People paste a vendor snippet into the wrong file, and their numbers stop making sense the moment a visitor clicks a link inside the app. Neither problem is hard once you know why it happens. So here is the whole thing: where the tag goes in the App Router, where it goes in the Pages Router, and why single page navigation quietly eats your data unless the tracker is built for it.

The short version

Load the tracking script once, high in the tree, so it runs on every route without being torn down and rebuilt on each navigation. In the App Router that means your root layout at app/layout.tsx. In the Pages Router it means pages/_app.tsx or the custom pages/_document.tsx. Use the Script component from next/script with the default afterInteractive strategy for a plain snippet, or the @next/third-parties package if you are on Google Analytics. Then verify one thing: that a client-side route change actually fires a pageview. That last check is where most setups are silently broken.

A plain script tag works, but next/script is better

You can drop a raw script tag into your layout and it will run. The reason I reach for the Script component instead is control over when it loads. A third-party file competing with your own JavaScript for the main thread during hydration is a real Core Web Vitals cost, and the framework gives you a knob for it.

The Script component takes a strategy prop with four values. afterInteractive is the default: it loads client-side after some hydration has happened, which is exactly what the docs recommend for analytics and tag managers. lazyOnload waits for browser idle time and suits low-priority background scripts like chat widgets. beforeInteractive loads before any first-party code and is reserved for things like consent managers and bot detectors. worker is still experimental and does not work with the App Router yet.

One rule the docs are strict about: a beforeInteractive script has to live in your root layout, and it is always injected into the document head no matter where you place it in the component. For ordinary measurement you almost never want beforeInteractive anyway. afterInteractive is the correct default.
See this on your own site — free for 14 days, no card.
Add My Website

App Router: the snippet goes in your root layout

In an App Router project, put the Script tag in app/layout.tsx so it is present on every page. A layout is a Server Component by default, and that is fine here: the Script component renders a normal script element on the server, so you do not need a use client directive just to load a tracker.

The shape is simple. Import Script from next/script, then render it inside the html body of your root layout with your tracker source and strategy set to afterInteractive. If you are on Google Analytics, skip the hand-rolled version entirely: install @next/third-parties and render the GoogleAnalytics component with your measurement id, the one that starts with G, in the root layout. Vercel maintains that package and it applies the loading strategy for you, fetching gtag.js after hydration.

The one place people go wrong here is reaching for onLoad or onReady to initialise the tracker. Those callbacks only work in Client Components, and they cannot be combined with beforeInteractive. If your snippet genuinely needs an onLoad callback, mark that specific component with use client. For a standard tag you will not need one at all.

Pages Router: same job, different file

If you are still on the Pages Router, and plenty of production apps are, the equivalent home for a site-wide script is pages/_app.tsx. Render the Script component there so it wraps every page. You can also use the custom document at pages/_document.tsx, and beforeInteractive scripts have been allowed there since version 12.2.2, but _app is usually the cleaner spot for a tracker.

The difference that matters between the two routers is not where the tag lives. It is how you detect navigation, which is the next section, and the Pages Router hands you a completely different API for it than the App Router does.

The gotcha nobody warns you about: route changes

This is the part that actually breaks analytics on a Next.js site, and it has nothing to do with which file the tag is in. The framework builds a single page application. When a visitor clicks an internal link, it swaps the page content on the client without a full browser reload. Your script loaded once, on the first page. It never runs again. So a session where someone visits four pages shows up as one pageview, and your whole funnel is wrong from day one.

How you fix it depends on the tracker. The good news for anyone on an Umami-based tracker, which includes Conclick, is that you do not fix it at all. The tracker patches the History API, watching pushState, replaceState and the popstate event, and sends a fresh pageview automatically on every client-side navigation. React, Next.js, Vue and Angular all work out of the box. The one thing you must not do is also call the track function yourself inside a useEffect, because then every navigation counts twice.

A raw Google Analytics gtag snippet is the opposite. Left alone it fires config a single time and stays silent after that. In the App Router the standard fix is a small Client Component that reads usePathname and useSearchParams from next/navigation and sends a pageview inside a useEffect whenever either value changes. There is a sharp edge here: useSearchParams pushes the component into client-side rendering, and if you do not wrap it in a Suspense boundary your static build will throw. Put the tracking component inside a Suspense boundary and that error disappears.

In the Pages Router the same job uses the router events API instead: subscribe to router.events with routeChangeComplete inside a useEffect in _app, and send a pageview with the new URL each time it fires. If you would rather not hand-write any of this for Google Analytics, the @next/third-parties GoogleAnalytics component leans on GA4 enhanced measurement, which records pageviews from browser history events once you enable the Page changes based on browser history events option in the GA admin panel. Confirm that box is ticked, because a fresh property does not always have it on.

Whatever tracker you use, test this before you trust a single number. Open your live analytics, click through three or four internal links, and watch whether each one registers as a separate pageview in real time. Two minutes of clicking saves you a month of decisions made on data that was quietly wrong.

Which tool is honest for a small Next.js site

You asked how to add measurement, not which vendor to buy, so I will keep this short and honest. For most small sites the real choice is between Google Analytics, a lightweight privacy-friendly tracker, and a product-analytics tool if you care about events and funnels more than pageviews.

Google Analytics is free, powerful and the default, and the @next/third-parties integration makes it genuinely easy to add. Its cost is complexity, sampling once a property gets large, and a consent story you have to manage. Umami, Plausible and similar tools trade some depth for a script that is smaller and simpler to reason about. I build Conclick, which runs on the open-source Umami engine, so treat this as the biased part: it measures without cookies, keeps a first-party visitor id in the browser's local storage rather than a cross-site cookie, and ties revenue back to the page and campaign that produced it. Whether that local storage id lets you skip a consent banner depends on your jurisdiction and configuration, may qualify for exemption in some cases, and none of this is legal advice.

The technical setup in this article is identical across all of them. Load the tag once in your layout, use next/script or the Google wrapper, and prove that route changes fire a pageview. Get those three right and the tool underneath is a preference, not a trap.

FAQ

Frequently asked questions

Where do I put the analytics script in a Next.js App Router project?

In your root layout at app/layout.tsx, so it loads once and stays mounted across every route. Use the Script component from next/script with the default afterInteractive strategy, or the GoogleAnalytics component from @next/third-parties if you are on Google Analytics. A root layout is a Server Component and the Script component renders there without a use client directive.

Why does my Next.js analytics only count the first page?

Because the framework navigates on the client without a full reload, so a script that ran once on the first page never runs again. You need route-change tracking. An Umami-based tracker does this automatically through the History API, while a raw Google Analytics snippet needs a client component watching usePathname and useSearchParams, or GA4 enhanced measurement with the browser-history option enabled.

Should I use a plain script tag or next/script?

Use next/script. A plain tag works, but the Script component lets you set a loading strategy so the analytics file does not fight your own JavaScript during hydration. afterInteractive is the right default for measurement. Reserve beforeInteractive for consent managers and bot detectors, and put those in the root layout.

How do I track route changes for Google Analytics in Next.js?

In the App Router, add a client component that reads usePathname and useSearchParams and sends a pageview in a useEffect on change, wrapped in a Suspense boundary so the static build does not throw. In the Pages Router, subscribe to router.events routeChangeComplete inside _app. Or use @next/third-parties and enable the browser-history option in GA4 enhanced measurement.

Do I need a use client directive to add an analytics script?

No, not for loading the script. The next/script Script component and the @next/third-parties components render fine from a Server Component layout. You only need use client for a component that calls hooks like usePathname, or that uses the onLoad and onError callbacks, since those do not work in Server Components.
Put this into practice

Conclick gives you privacy-first analytics, heatmaps, funnels, and revenue attribution in one. Free for 14 days, no card.

Sources

  1. 01Next.js: Script Component API reference (strategies, beforeInteractive placement)nextjs.org
  2. 02Next.js: Third-party libraries (GoogleAnalytics, pageview tracking on history changes)nextjs.org
  3. 03Next.js: useSearchParams (Suspense boundary requirement)nextjs.org
  4. 04Umami docs: Track a single-page application (History API auto-tracking)docs.umami.is
  5. 05Next.js: Using Google Analytics through @next/third-parties/googlenextjs.org
D
Written by Deepak Yadav
Founder, Conclick

Deepak Yadav is the founder of Conclick — privacy-first web analytics that ties every visit to real revenue. He has spent years staring at GA4 dashboards trying to answer one question (which traffic actually makes money) and built Conclick to answer it. He writes about analytics, attribution, and what actually moves the needle for bootstrapped founders.

Read next
consent.
Privacy·7 min read
Do Heatmaps Need Cookie Consent? What Each Tool Actually Sets
bots.
Measurement·8 min read
How to Filter Bot Traffic From Analytics: 2026 Guide
migration.
Guide·9 min read
Migrating off Google Analytics 4: a practical guide
gdpr.
Guide·10 min read
The GDPR-Compliant Analytics Checklist for 2026
cited.
GEO·9 min read
How to Get Cited by ChatGPT: What Actually Moves the Needle
funnel.
Guide·10 min read
How to Read a Conversion Funnel and Fix the Biggest Leak
Get started

See which traffic actually makes you money

Analytics, heatmaps, funnels, and revenue in one privacy-first dashboard. Free for 14 days, no card.

Conclick
Quick Links
  • Advantages
  • Features
  • Benefits
  • Reviews
  • FAQ
Pages
  • Home
  • Pricing
  • Contact
  • About
Resources
  • Blogs
  • Guide
  • Compare
  • Tools
  • Alternatives
  • Glossary
  • Use cases
Social
  • Linkedin
  • Twitter
Legal
  • Privacy policy
  • Terms of service
Copyright © 2026 Conclick. All rights reserved