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 State | Phone | External ID | Expected EMQ | |
|---|---|---|---|---|
| Guest | ❌ | ❌ | ❌ | 1.0-2.0 |
| Guest + fbclid | ❌ | ❌ | ❌ | 2.5-3.5 |
| Logged-in | ✅ | ❌ | ✅ | 4.5-6.0 |
| Logged-in + Phone | ✅ | ✅ | ✅ | 7.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
- Lazy load on non-critical pages:
const AnacoicScript = dynamic(
() => import('@/components/anacoic-script'),
{ ssr: false }
);
- Preconnect to edge endpoint:
<head>
<link rel="preconnect" href="https://edge.anacoic.com" />
</head>
- Debounce rapid events:
import { debounce } from 'lodash';
const debouncedTrack = useMemo(
() => debounce(track, 300),
[track]
);