# Analytics initializes only on the client side

- **Pattern:** `ab-001698` (`marketing-analytics.core-analytics.analytics-initialized-client-side`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001698
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001698)

## 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 `useEffect` hooks (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">` or `strategy="lazyOnload"` (correct) vs. `strategy="beforeInteractive"` (acceptable but unusual for analytics)
- **Pass criteria:** All analytics initialization code is guarded from running during SSR, either via `useEffect`, `'use client'` directive, `typeof window` guard, 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 a `useEffect` (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-present` failed), 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 `useEffect` hook or a component marked with `'use client'`:

  ```tsx
  // 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>` with `strategy="afterInteractive"` which automatically handles the SSR/client split.

Taxons: observability, operational-readiness

HTML version: https://auditbuffet.com/patterns/ab-001698
