React/Next.js Analytics Integration Patterns
Next.js Complicates the Picture With Mixed Rendering
Next.js App Router supports Server Components, Client Components, Static Rendering, and Server-Side Rendering simultaneously within one app — which means tracking integration can't assume a single consistent rendering model. GTM/gtag snippets must load in a Client Component (or via next/script), while server-originated events (for financial accuracy, mirroring the Shopify/WooCommerce webhook pattern) belong in Server Actions, Route Handlers, or middleware, depending on where the authoritative business event actually occurs.
Loading GTM Correctly With next/script
Next.js's built-in next/script component with the strategy prop controls load timing relative to hydration. For GTM, strategy="afterInteractive" is the standard choice — it loads after the page becomes interactive, avoiding render-blocking behavior while still firing early enough to capture most meaningful page load events. Using the default unmanaged <script> tag or strategy="beforeInteractive" for GTM is usually unnecessary and can hurt Core Web Vitals (particularly Interaction to Next Paint) for no analytical benefit.
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Script
id="gtm-script"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');`,
}}
/>
{children}
</body>
</html>
);
}Virtual Pageviews With usePathname and useSearchParams
Because the App Router doesn't fire a full page reload on navigation, you need a client-side listener on route changes using the usePathname and useSearchParams hooks combined inside a useEffect, pushed into a shared layout or a dedicated analytics tracking component rendered once near the root.
"use client";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
export function PageViewTracker() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
const url = pathname + (searchParams.toString() ? `?${searchParams.toString()}` : "");
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: "page_view",
page_path: url,
page_title: document.title,
page_location: window.location.origin + url,
});
}, [pathname, searchParams]);
return null;
}useSearchParams Forces Client-Side Rendering of the Boundary
useSearchParams must be wrapped in a <Suspense> boundary in the App Router, or the build will throw a deopt warning/error for static pages. Wrap the PageViewTracker component in <Suspense fallback={null}> at the layout level to avoid forcing unrelated static pages into dynamic rendering just because of the tracker.Server Actions as a Tracking Boundary
For events tied to genuine server-side mutations (form submissions, checkout completion handled via a Server Action rather than a client-side API call), firing the corresponding Measurement Protocol or Conversions API call directly inside the Server Action — after the mutation succeeds, before returning to the client — guarantees the event only fires on confirmed success and isn't dependent on the client's JS execution completing. This mirrors the webhook pattern used for Shopify/WooCommerce but using Next.js's own server execution context instead of an external webhook.
Don't Track Server Component Renders as Pageviews
What's Next?
With web tracking patterns well understood, mobile app attribution introduces a completely different set of constraints — no persistent cookies, app store privacy frameworks, and MMP-mediated attribution — worth exploring as the next distinct measurement domain.
I build these systems professionally.
Whether it's a RAG pipeline, analytics migration, or AI workflow — let's talk.