React & Next.js

Integrate Anacoic with React and Next.js applications

React & Next.js Integration

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/tracker.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/tracker.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/tracker.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