> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiveku.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Google Analytics to Your Site

> Install GA4 tracking on your Hiveku-hosted site

Add Google Analytics 4 to track traffic, conversions, and user behavior on your site.

<Info>
  Hiveku already includes privacy-friendly visitor analytics — see [Track Site Analytics](/how-tos/track-analytics). Use Google Analytics when you need GA-specific integrations (Google Ads conversions, advanced audiences, BigQuery export) or when your team is already on GA.
</Info>

## Get Your Measurement ID

Before installing, grab your GA4 Measurement ID from [analytics.google.com](https://analytics.google.com):

1. Admin > Property > Data streams
2. Click your web stream
3. Copy the Measurement ID — it looks like `G-XXXXXXXXXX`

## Three Ways to Install

<Tabs>
  <Tab title="AI Chat (Easiest)">
    Ask the AI in your project:

    ```
    Add Google Analytics GA4 tracking with ID G-XXXXXXXXXX to my site. 
    Put the snippet in the root layout so it's on every page.
    ```

    The AI picks the right file for your stack (Next.js root layout, static site `<head>`, etc.), adds the snippet, and deploys when you confirm.
  </Tab>

  <Tab title="Manual Snippet">
    Paste the official GA4 snippet in your site's `<head>`:

    ```html theme={null}
    <!-- Google tag (gtag.js) -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'G-XXXXXXXXXX');
    </script>
    ```

    Replace `G-XXXXXXXXXX` with your real Measurement ID.

    For Next.js, put this in `app/layout.tsx` or `pages/_document.tsx`. For static sites, put it in the `<head>` of your base template.
  </Tab>

  <Tab title="Next.js (@next/third-parties)">
    Next.js has a first-party helper that handles the script tag and lazy loading:

    ```bash theme={null}
    npm install @next/third-parties
    ```

    ```typescript theme={null}
    // app/layout.tsx
    import { GoogleAnalytics } from '@next/third-parties/google';

    export default function RootLayout({ children }) {
      return (
        <html>
          <body>{children}</body>
          <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID!} />
        </html>
      );
    }
    ```

    Set `NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX` as an [env var](/how-tos/env-vars). This gives you cleaner code and better performance than the manual snippet.
  </Tab>
</Tabs>

## Using an Env Var

Whichever path you choose, it's a good idea to read the Measurement ID from an environment variable rather than hardcoding it. This way, production uses one GA property and staging uses another (or none):

```html theme={null}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ NEXT_PUBLIC_GA_ID }}"></script>
```

See [Configure Environment Variables](/how-tos/env-vars) for how to set per-environment values.

## Cookie Consent & Privacy

GA4 uses cookies to track users across sessions. Depending on your visitors' location, you may need a consent banner before loading GA:

* **EU (GDPR)** — explicit opt-in required
* **California (CCPA)** — opt-out required, at minimum
* **Other regions** — varies, check local law

<Warning>
  Don't load GA before the user consents. The easiest path is to enable the cookie consent banner in [Site Enhancements](/how-tos/site-enhancements) and gate the GA snippet on it — the AI can wire this up for you.
</Warning>

### The consent API

Hiveku's consent banner exposes exactly these globals on your site:

| What                                | Value                                                                                                                                   |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `window.__hivekuConsent`            | The visitor's stored decision: `{ v, ts, mode, gpc, categories }`. Undefined until a decision exists.                                   |
| `window.__hivekuConsent.categories` | Booleans keyed by category: `necessary`, `analytics`, `advertisement`, `functionality`.                                                 |
| `hiveku-consent-change`             | A window event fired on every decision. `event.detail` is `{ action, categories }`.                                                     |
| `window.__hivekuConsentGPC`         | `true` when the browser sent a Global Privacy Control signal. Advertising stays denied in that case regardless of what the banner says. |

<Warning>
  There is no `window.hiveku.consent`. If you gate on that, the condition is permanently `undefined` — permanently falsy — and your analytics never initialises. Use `window.__hivekuConsent` (two leading underscores).
</Warning>

### Gating the GA snippet

The consent runtime is loaded asynchronously, so reading the global once at page load usually runs *before* consent resolves and you get a false negative. Check the global once — the inline bootstrap sets it synchronously for visitors who already have a stored decision — then listen for the event to cover everyone else:

```html theme={null}
<script>
  (function () {
    var loaded = false;

    function loadGA() {
      if (loaded) return;
      loaded = true;
      var gaScript = document.createElement('script');
      gaScript.async = true;
      gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
      document.head.appendChild(gaScript);
      window.dataLayer = window.dataLayer || [];
      function gtag() { window.dataLayer.push(arguments); }
      gtag('js', new Date());
      gtag('config', 'G-XXXXXXXXXX');
    }

    function analyticsGranted(categories) {
      return !!(categories && categories.analytics);
    }

    if (analyticsGranted(window.__hivekuConsent && window.__hivekuConsent.categories)) {
      loadGA();
    }

    window.addEventListener('hiveku-consent-change', function (event) {
      if (analyticsGranted(event.detail && event.detail.categories)) loadGA();
    });
  })();
</script>
```

<Info>
  You only need this hand-written gate when you paste the GA snippet into your own code. If you register GA4 as a tracker in [Site Enhancements](/how-tos/site-enhancements) instead, the consent banner injects the tag for you once the Analytics category is granted, and sets Google Consent Mode v2 defaults before any Google tag can run.
</Info>

## Google Tag Manager

If you prefer Tag Manager, the install flow is identical — swap the `gtag.js` snippet for your GTM container snippet:

```html theme={null}
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){ /* GTM snippet */ })(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->
```

Everything else in this guide applies the same way.

## Verify It's Working

<Steps>
  <Step title="Deploy your changes">
    Any of the three install paths above requires a deploy.
  </Step>

  <Step title="Open the Realtime report in GA4">
    analytics.google.com > your property > Reports > Realtime.
  </Step>

  <Step title="Visit your site in a new tab">
    Load any page. Within \~30 seconds, you should see your session appear in the Realtime report.
  </Step>

  <Step title="Check the script loaded">
    Open DevTools > Network > filter for `gtag` — you should see `gtag/js?id=G-XXXXXXXXXX` returning 200.
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="No data showing after 24 hours">
    Check three things: (1) Measurement ID format is `G-` followed by 10 characters — older UA-prefixed IDs are discontinued; (2) the `gtag/js` script loads successfully in DevTools Network — if it 404s, the ID is wrong; (3) the snippet is in the rendered HTML, not just the source — a client-rendered React app may need special placement.
  </Accordion>

  <Accordion title="Nothing loads once the consent banner is on">
    Your gate is reading a global that doesn't exist. The only consent global is `window.__hivekuConsent` — `window.hiveku.consent` is `undefined`, so the check never passes. Check it in the DevTools console after accepting the banner, and make sure you also listen for `hiveku-consent-change` rather than reading the global once at load: the consent runtime resolves asynchronously, so a single synchronous read races it and loses.
  </Accordion>

  <Accordion title="Works in dev but not production">
    Check your env var — `NEXT_PUBLIC_GA_ID` has to be set in the production environment, not just locally. Vars without the `NEXT_PUBLIC_` prefix aren't exposed to the browser.
  </Accordion>

  <Accordion title="Ad blockers blocking tracking">
    Expected for a non-trivial slice of users. Mitigations include server-side Measurement Protocol for important events (conversions) and using Google Tag Manager's server-side container. Don't try to defeat ad blockers for general pageviews — you'll erode user trust.
  </Accordion>

  <Accordion title="Duplicate events in reports">
    The GA snippet was added twice — commonly in both the root layout and a page template. Search your codebase for `G-` and remove duplicates.
  </Accordion>

  <Accordion title="Realtime works but reports are empty after hours">
    GA4 processes data in batches — full reports can lag 24–48 hours after first install. Realtime is the best quick-check for "is it working."
  </Accordion>
</AccordionGroup>

## What's Next?

<CardGroup cols={2}>
  <Card title="Track Site Analytics" icon="chart-line" href="/how-tos/track-analytics">
    Use Hiveku's built-in privacy-friendly analytics
  </Card>

  <Card title="Environment Variables" icon="key" href="/how-tos/env-vars">
    Store your Measurement ID per environment
  </Card>
</CardGroup>
