Analytics initializes only on the client side
Why it matters
Analytics SDKs reach for window, document, and localStorage during initialization. When that code runs inside a React Server Component or at module top-level in an SSR file, the server throws ReferenceError: window is not defined and the page either 500s or renders a broken shell. Users see a white screen; crawlers see an error; Vercel logs fill with the same stack trace. The analytics tool you installed to observe the site is actively preventing it from rendering.
Severity rationale
High because SSR-time crashes take entire pages offline and the fix is small but mandatory.
Remediation
Move all SDK initialization into a component marked 'use client' and wrap the call in useEffect(() => { initializeAnalytics() }, []) so it runs only after hydration. For script-tag analytics like GA4 or GTM, use Next.js <Script strategy="afterInteractive"> which defers loading until the client is ready. See components/analytics-provider.tsx as the single initialization site.
Detection
-
ID:
analytics-initialized-client-side -
Severity:
high -
What to look for: In SSR frameworks (Next.js, Nuxt, Remix, SvelteKit), analytics SDKs that access browser globals (
window,document,localStorage) must not be initialized during server-side rendering. Look for:- Analytics initialization inside
useEffecthooks (correct) - Analytics initialization at module top-level in a file used server-side (incorrect)
typeof window !== 'undefined'guards around analytics initialization (correct)- In Next.js App Router: analytics in a
'use client'component (correct) vs. in a Server Component (incorrect) - GTM/GA scripts loaded via
<Script strategy="afterInteractive">orstrategy="lazyOnload"(correct) vs.strategy="beforeInteractive"(acceptable but unusual for analytics)
- Analytics initialization inside
-
Pass criteria: All analytics initialization code is guarded from running during SSR, either via
useEffect,'use client'directive,typeof windowguard, or appropriate<Script>strategy. Count every analytics initialization call and verify each has at least 1 SSR guard. -
Fail criteria: Analytics SDK initialization code exists at module top-level in a server-rendered context, OR analytics is called inside a React Server Component without a client guard. Do NOT pass if the
'use client'directive is present on the file but the analytics initialization runs outside auseEffect(top-level module execution in a'use client'file still runs during SSR in streaming scenarios). -
Skip (N/A) when: No analytics is present (
script-presentfailed), OR the project is a purely client-rendered SPA (no SSR), OR analytics is loaded only via external script tags with no SDK initialization code. -
Detail on fail:
"Analytics initialization found in a Server Component or at module top-level without a window guard. This will throw errors during SSR and may prevent the page from rendering." -
Remediation: Move all analytics SDK initialization into a
useEffecthook or a component marked with'use client':// components/analytics-provider.tsx 'use client' import { useEffect } from 'react' export function AnalyticsProvider() { useEffect(() => { // Safe to initialize here — runs only in the browser initializeAnalytics() }, []) return null }For script-based analytics (GA4, GTM), use Next.js
<Script>withstrategy="afterInteractive"which automatically handles the SSR/client split.
Taxons
History
- 2026-04-18·v1.0.0·Initial import from marketing-analytics·automated