Platform Guides

React & Next.js

Integrate Anacoic with React and Next.js applications

Doc #5

Modern React integration guide for SPA and SSR applications.

Prerequisites

  • React 18+ or Next.js 13+
  • Anacoic account with gateway
  • Understanding of React hooks and effects

Basic React Integration

Step 1: Create Tracker Hook

Create useAnacoic.js:

import { useEffect, useRef, useCallback } from 'react';

const GATEWAY_ID = process.env.REACT_APP_ANACOIC_GATEWAY_ID;

export function useAnacoic() {
  const isLoaded = useRef(false);

  // Load tracker script
  useEffect(() => {
    if (isLoaded.current || typeof window === 'undefined') return;

    // Check if already loaded
    if (window.anacoic) {
      isLoaded.current = true;
      return;
    }

    // Create script element
    const script = document.createElement('script');
    script.src = 'https://edge.anacoic.com/v1/ana.js';
    script.setAttribute('data-gateway', GATEWAY_ID);
    script.setAttribute('data-emq', 'optimize');
    script.async = true;

    script.onload = () => {
      isLoaded.current = true;
    };

    document.head.appendChild(script);

    return () => {
      // Cleanup if needed
    };
  }, []);

  // Track event function
  const track = useCallback((eventName, properties = {}) => {
    if (typeof window === 'undefined' || !window.anacoic) return;

    window.anacoic.track({
      event_name: eventName,
      ...properties
    });
  }, []);

  // Track page view
  const trackPageView = useCallback((pageName, properties = {}) => {
    track(pageName || 'PageView', properties);
  }, [track]);

  return { track, trackPageView, isLoaded: isLoaded.current };
}

Step 2: Use in Components

import { useAnacoic } from './useAnacoic';

function PurchaseButton({ order }) {
  const { track } = useAnacoic();

  const handlePurchase = async () => {
    // Process purchase...

    // Track with customer data for high EMQ
    track('Purchase', {
      user: {
        email: order.customerEmail,
        phone: order.customerPhone,
        external_id: order.customerId
      },
      custom_data: {
        value: order.total,
        currency: order.currency,
        transaction_id: order.id
      }
    });
  };

  return <button onClick={handlePurchase}>Complete Purchase</button>;
}

Next.js Integration

App Router (Next.js 13+)

Create components/anacoic-provider.tsx:

'use client';

import Script from 'next/script';
import { createContext, useContext, useCallback } from 'react';

const AnacoicContext = createContext(null);

export function AnacoicProvider({
  children,
  gatewayId
}: {
  children: React.ReactNode;
  gatewayId: string;
}) {
  const track = useCallback((eventName: string, properties?: any) => {
    if (typeof window !== 'undefined' && window.anacoic) {
      window.anacoic.track({
        event_name: eventName,
        ...properties
      });
    }
  }, []);

  return (
    <AnacoicContext.Provider value={{ track }}>
      {children}
      <Script
        src="https://edge.anacoic.com/v1/ana.js"
        data-gateway={gatewayId}
        data-emq="optimize"
        strategy="afterInteractive"
      />
    </AnacoicContext.Provider>
  );
}

export const useAnacoic = () => {
  const context = useContext(AnacoicContext);
  if (!context) {
    throw new Error('useAnacoic must be used within AnacoicProvider');
  }
  return context;
};

Update app/layout.tsx:

import { AnacoicProvider } from '@/components/anacoic-provider';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <AnacoicProvider gatewayId={process.env.NEXT_PUBLIC_ANACOIC_GATEWAY_ID!}>
          {children}
        </AnacoicProvider>
      </body>
    </html>
  );
}

Pages Router (Next.js 12/13)

Update pages/_app.tsx:

import Script from 'next/script';
import { useRouter } from 'next/router';
import { useEffect } from 'react';

function MyApp({ Component, pageProps }) {
  const router = useRouter();

  // Track page views
  useEffect(() => {
    const handleRouteChange = (url: string) => {
      if (window.anacoic) {
        window.anacoic.track({
          event_name: 'PageView',
          context: { path: url }
        });
      }
    };

    router.events.on('routeChangeComplete', handleRouteChange);
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange);
    };
  }, [router.events]);

  return (
    <>
      <Component {...pageProps} />
      <Script
        src="https://edge.anacoic.com/v1/ana.js"
        data-gateway={process.env.NEXT_PUBLIC_ANACOIC_GATEWAY_ID}
        data-emq="optimize"
        strategy="afterInteractive"
      />
    </>
  );
}

export default MyApp;

TypeScript Types

Create types/anacoic.d.ts:

declare global {
  interface Window {
    anacoic: {
      track: (event: AnacoicEvent) => void;
      page: (name?: string, props?: any) => void;
      identify: (userId: string, traits?: any) => void;
    };
  }
}

interface AnacoicEvent {
  event_name: string;
  event_id?: string;
  user?: {
    email?: string;
    phone?: string;
    external_id?: string;
    client_ip_address?: string;
    client_user_agent?: string;
    fbp?: string;
    fbc?: string;
  };
  custom_data?: Record<string, any>;
  context?: {
    url?: string;
    path?: string;
    referrer?: string;
  };
}

export {};

Server-Side Tracking (Next.js API Routes)

For secure server-side events:

// app/api/track/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const body = await request.json();

  const payload = {
    event_name: body.event_name,
    event_id: body.event_id || crypto.randomUUID(),
    timestamp: Math.floor(Date.now() / 1000),
    user: {
      email: body.email,  // Server knows this from session
      client_ip_address: request.ip,
      client_user_agent: request.headers.get('user-agent')
    },
    custom_data: body.custom_data
  };

  const response = await fetch('https://edge.anacoic.com/track', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Gateway-ID': process.env.ANACOIC_GATEWAY_ID!
    },
    body: JSON.stringify(payload)
  });

  return NextResponse.json({ success: response.ok });
}

Use from client:

// Server-side tracking for sensitive events
await fetch('/api/track', {
  method: 'POST',
  body: JSON.stringify({
    event_name: 'Purchase',
    email: user.email,  // Server adds rest
    custom_data: { value: 99.99, currency: 'USD' }
  })
});

React Hook Examples

useCartTracking

export function useCartTracking() {
  const { track } = useAnacoic();

  const addToCart = useCallback((product) => {
    track('AddToCart', {
      custom_data: {
        content_name: product.name,
        content_type: 'product',
        content_ids: [product.id],
        value: product.price,
        currency: 'USD'
      }
    });
  }, [track]);

  const initiateCheckout = useCallback((cart) => {
    track('InitiateCheckout', {
      custom_data: {
        value: cart.total,
        currency: cart.currency,
        num_items: cart.items.length
      }
    });
  }, [track]);

  return { addToCart, initiateCheckout };
}

useUserTracking

export function useUserTracking(user: User | null) {
  const { track } = useAnacoic();

  useEffect(() => {
    if (user) {
      // Identify user for future events
      if (window.anacoic) {
        window.anacoic.identify(user.id, {
          email: user.email,
          name: user.name
        });
      }
    }
  }, [user]);

  const trackLogin = useCallback(() => {
    track('Login', {
      user: user ? { external_id: user.id } : undefined
    });
  }, [track, user]);

  const trackSignup = useCallback(() => {
    track('CompleteRegistration', {
      user: user ? {
        email: user.email,
        external_id: user.id
      } : undefined
    });
  }, [track, user]);

  return { trackLogin, trackSignup };
}

EMQ Optimization

Pass User Data from Auth Context

function CheckoutForm() {
  const { user } = useAuth();  // Your auth context
  const { track } = useAnacoic();

  const handleSubmit = async (orderData) => {
    // Track with full user data for high EMQ
    track('Purchase', {
      event_id: `${orderData.id}_${Date.now()}`,
      user: user ? {
        email: user.email,
        phone: user.phone,
        external_id: user.id
      } : undefined,
      custom_data: {
        value: orderData.total,
        currency: orderData.currency,
        transaction_id: orderData.id
      }
    });
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

Expected EMQ Scores

User StateEmailPhoneExternal IDExpected EMQ
Guest1.0-2.0
Guest + fbclid2.5-3.5
Logged-in4.5-6.0
Logged-in + Phone7.5-9.0

Environment Variables

# .env.local
NEXT_PUBLIC_ANACOIC_GATEWAY_ID=your_gateway_id_here

# Server-only (for API routes)
ANACOIC_GATEWAY_ID=your_gateway_id_here

Testing

Jest/Mock

// __mocks__/anacoic.ts
global.window.anacoic = {
  track: jest.fn(),
  page: jest.fn(),
  identify: jest.fn()
};

// In tests
import { renderHook } from '@testing-library/react';
import { useAnacoic } from './useAnacoic';

test('tracks events', () => {
  const { result } = renderHook(() => useAnacoic());
  result.current.track('Test');
  expect(window.anacoic.track).toHaveBeenCalled();
});

Performance Tips

  1. Lazy load on non-critical pages:
const AnacoicScript = dynamic(
  () => import('@/components/anacoic-script'),
  { ssr: false }
);
  1. Preconnect to edge endpoint:
<head>
  <link rel="preconnect" href="https://edge.anacoic.com" />
</head>
  1. Debounce rapid events:
import { debounce } from 'lodash';

const debouncedTrack = useMemo(
  () => debounce(track, 300),
  [track]
);

Next Steps

No analytics or marketing tags load until you opt in.

We use a first-party consent setting to remember your choice. If you allow analytics or marketing, Google Tag Manager can load the tags configured for this site. You can change the decision any time from the footer.