# React GA4 Best Practices: Integrating Google Analytics 4 with gtag.js in React Applications

> Implement React GA4 with gtag js using the Firebase Analytics SDK. Discover best practices for script injection, initialization, and event queuing with a custom hook and route tracker. Learn more.

- Repository: [Firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk)
- Tags: best-practices
- Published: 2026-02-16

---

**Use the Firebase Analytics SDK as a type-safe wrapper around gtag.js to automatically handle script injection, initialization, and event queuing while implementing a custom hook and route tracker for React-specific navigation patterns.**

Implementing React GA4 (Google Analytics 4) in modern React applications requires careful handling of client-side routing and script initialization. The `firebase/firebase-js-sdk` provides a robust Analytics package that wraps the standard gtag.js library, offering TypeScript support and automatic script injection while maintaining full compatibility with GA4's event model.

## Recommended React GA4 Architecture

Organize your React GA4 implementation into three layers to ensure type safety and separation of concerns:

```

src/
 ├─ firebase/
 │    └─ firebase.ts            // FirebaseApp + Analytics initialization
 ├─ hooks/
 │    └─ useAnalytics.ts        // Returns the Analytics instance
 ├─ components/
 │    └─ PageViewTracker.tsx   // Logs page_view on route changes
 └─ App.tsx                     // Root component

```

**Why this structure works:**

- **Dedicated Firebase module:** Keeps configuration in one place and guarantees initialization logic in [`packages/analytics/src/initialize-analytics.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/analytics/src/initialize-analytics.ts) runs only once. This module handles automatic insertion of the `<script src="https://www.googletagmanager.com/gtag/js?id=G-XXXX">` tag if not already present【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/initialize-analytics.ts#L22-L27】.

- **Custom hook:** Provides a type-safe way to access the Analytics instance after the internal initialization promise resolves, preventing "gtag is not a function" errors during the loading phase.

- **Route tracker component:** GA4 sends an initial `page_view` automatically, but for **client-side routing** (React Router, Next.js, etc.), you must manually fire it on navigation. A component with `useEffect` listening to the router's location does that reliably.

## Step-by-Step React GA4 Implementation

### Initialize Firebase and Analytics

Create a configuration module that initializes the Firebase app and wraps the gtag.js initialization:

```typescript
// src/firebase/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAnalytics, SettingsOptions } from 'firebase/analytics';

const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_PROJECT.firebaseapp.com',
  projectId: 'YOUR_PROJECT',
  appId: 'YOUR_APP_ID',
  measurementId: 'G-XXXXXXX', // GA4 measurement ID
};

const app = initializeApp(firebaseConfig);

// Optional: customize gtag/dataLayer names to avoid global conflicts
const analyticsSettings: SettingsOptions = {
  gtagName: 'gtag',
  dataLayerName: 'dataLayer'
};

export const analytics = getAnalytics(app, analyticsSettings);

```

The `getAnalytics` call triggers the logic in [`packages/analytics/src/initialize-analytics.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/analytics/src/initialize-analytics.ts), which:
1. Detects existing gtag scripts via `findGtagScriptOnPage`【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/helpers.ts#L33-L45】
2. Inserts the script tag if missing
3. Queues the `gtag('js', …)` and `gtag('config', measurementId, …)` commands【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/initialize-analytics.ts#L34-L57】【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/initialize-analytics.ts#L138-L155】

### Create the useAnalytics Hook

Implement a hook that safely exposes the Analytics instance:

```typescript
// src/hooks/useAnalytics.ts
import { useEffect, useState } from 'react';
import { analytics } from '../firebase/firebase';
import { Analytics } from 'firebase/analytics';

export function useAnalytics(): Analytics | null {
  const [instance, setInstance] = useState<Analytics | null>(null);

  useEffect(() => {
    // The SDK queues calls automatically, so we can set immediately
    setInstance(analytics);
  }, []);

  return instance;
}

```

### Track Page Views in React Router

Create a component that logs `page_view` events on route changes:

```tsx
// src/components/PageViewTracker.tsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { logEvent } from 'firebase/analytics';
import { useAnalytics } from '../hooks/useAnalytics';

export default function PageViewTracker() {
  const location = useLocation();
  const analytics = useAnalytics();

  useEffect(() => {
    if (!analytics) return;
    
    logEvent(analytics, 'page_view', {
      page_path: location.pathname + location.search,
      page_title: document.title,
    });
  }, [location, analytics]);

  return null;
}

```

Mount this component inside your router in [`App.tsx`](https://github.com/firebase/firebase-js-sdk/blob/main/App.tsx) to ensure every navigation triggers a GA4 page view.

### Log Custom Events

Use the typed `logEvent` function for e-commerce or custom interactions:

```tsx
import { logEvent } from 'firebase/analytics';
import { useAnalytics } from '../hooks/useAnalytics';

function AddToCartButton({ product }) {
  const analytics = useAnalytics();

  const handleAdd = () => {
    if (analytics) {
      logEvent(analytics, 'add_to_cart', {
        items: [{ 
          item_id: product.id, 
          item_name: product.name, 
          price: product.price 
        }],
        currency: 'USD',
      });
    }
  };

  return <button onClick={handleAdd}>Add to cart</button>;
}

```

The `logEvent` helper in [`packages/analytics/src/api.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/analytics/src/api.ts) wraps the underlying `gtag('event', …)` call and includes error handling via the SDK's logger【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/api.ts#L75-L100】.

## Advanced React GA4 Best Practices

| Practice | Implementation Details | Source Reference |
|----------|------------------------|------------------|
| **Custom gtag/dataLayer names** | Pass `gtagName` and `dataLayerName` in `SettingsOptions` to `getAnalytics` to avoid global namespace collisions with other tracking tools. | [`public-types.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/public-types.ts) defines `SettingsOptions`【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/public-types.ts#L23-L28】 |
| **Default event parameters** | Call `setDefaultEventParameters` immediately after initialization to attach common context (app version, user tier) to every subsequent event. | [`api.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/api.ts) implements this via the wrapped gtag function【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/api.ts#L57-L63】 |
| **User identification** | Use `setUserId` and `setUserProperties` to enable cross-device reporting and audience segmentation. These map to `gtag('config', …)` updates. | [`api.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/api.ts) handles these config updates【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/api.ts#L89-L108】 |
| **Consent management** | Configure `defaultConsentSettingsForInit` during initialization for GDPR/CCPA compliance, then update via `gtag('consent', 'update', …)`. | [`initialize-analytics.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/initialize-analytics.ts) processes default consent【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/initialize-analytics.ts#L28-L33】 |
| **Duplicate script prevention** | The SDK automatically checks for existing gtag scripts before injection to prevent "gtag is not a function" errors. | [`helpers.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/helpers.ts) contains `findGtagScriptOnPage`【/cache/repos/github.com/firebase/firebase-js-sdk/main/packages/analytics/src/helpers.ts#L33-L45】 |
| **Performance optimization** | Lazy-load the analytics module to reduce initial bundle size. | Import `firebase/analytics` only in components that need it, or use dynamic `import()`. |

## Summary

- **Use Firebase Analytics as your React GA4 wrapper** – it handles gtag.js script injection, initialization, and provides TypeScript types via [`packages/analytics/src/public-types.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/analytics/src/public-types.ts).
- **Structure your code** with a dedicated Firebase config module, a `useAnalytics` hook for safe instance access, and a `PageViewTracker` component to handle React Router navigation.
- **Initialize once** by calling `getAnalytics` in your config module; the SDK automatically prevents duplicate script tags using `findGtagScriptOnPage` in [`packages/analytics/src/helpers.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/analytics/src/helpers.ts).
- **Track navigation manually** because GA4 only sends the initial `page_view` automatically; use `logEvent` from [`packages/analytics/src/api.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/analytics/src/api.ts) on route changes.
- **Configure advanced features** like custom gtag names, default parameters, user properties, and consent settings via the `SettingsOptions` interface and helper functions defined in the SDK source.

## Frequently Asked Questions

### How do I prevent duplicate gtag.js script tags when using React GA4 with Firebase?

The Firebase Analytics SDK automatically detects existing gtag scripts before injection. In [`packages/analytics/src/helpers.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/analytics/src/helpers.ts), the `findGtagScriptOnPage` function checks for existing `<script>` tags with the gtag URL, and `initializeAnalytics` in [`packages/analytics/src/initialize-analytics.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/analytics/src/initialize-analytics.ts) only calls `insertScriptTag` if none are found. This prevents the "gtag is not a function" errors that occur when the library loads twice.

### Why do I need to manually track page views in a React single-page application?

GA4 sends an automatic `page_view` event only on the initial browser page load. In React applications using client-side routing (React Router, Next.js, etc.), subsequent navigation does not trigger a full page reload, so GA4 never receives additional page view events. You must manually call `logEvent(analytics, 'page_view', { page_path: location.pathname, page_title: document.title })` inside a component that listens to route changes, as implemented in the `PageViewTracker` pattern.

### How do I set custom gtag or dataLayer names to avoid conflicts with other tracking tools?

Pass a `SettingsOptions` object as the second argument to `getAnalytics` in your initialization module. The [`public-types.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/public-types.ts) file defines this interface with `gtagName` and `dataLayerName` properties, which the SDK passes to the internal initialization logic in [`initialize-analytics.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/initialize-analytics.ts). This prevents namespace collisions when your page already loads Google Tag Manager or other analytics libraries that define their own global `gtag` functions.

### What is the best way to handle GDPR or CCPA consent in React GA4 implementations?

Configure default consent settings during initialization by including consent parameters in your `AnalyticsSettings`, which [`initialize-analytics.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/initialize-analytics.ts) processes via `defaultConsentSettingsForInit` before sending the initial `gtag('config')` command. For dynamic updates (when a user accepts or rejects cookies), call the underlying `gtag('consent', 'update', { analytics_storage: 'granted' })` command using the analytics instance obtained from `useAnalytics`. The SDK's error handling in [`api.ts`](https://github.com/firebase/firebase-js-sdk/blob/main/api.ts) ensures these calls fail gracefully if the script hasn't loaded.