SDK — Next.js
Install `@layers/client` in a Next.js app. App Router and Pages Router patterns. SSR caveats. Auto-screen via `usePathname()`.
The Layers SDK works in Next.js apps with the standard @layers/client package — there is no Next.js-specific package today. This guide shows the App Router pattern (the default in Next.js 13+) and the Pages Router fallback for legacy projects.
Install
npm i @layers/clientApp Router (Next.js 13+)
The SDK runs only in client components — it depends on window for the event queue. Wrap the SDK init in a client provider that mounts at the root layout.
app/layers-provider.tsx
'use client';
import { LayersClient } from '@layers/client';
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
import { usePathname } from 'next/navigation';
const LayersContext = createContext<LayersClient | null>(null);
export function LayersProvider({ children }: { children: ReactNode }) {
const [client, setClient] = useState<LayersClient | null>(null);
const pathname = usePathname();
useEffect(() => {
const c = new LayersClient({
apiKey: 'unused-but-required-by-type',
appId: process.env.NEXT_PUBLIC_LAYERS_APP_ID!,
environment: process.env.NODE_ENV === 'production' ? 'production' : 'development',
});
c.init().then(() => setClient(c));
}, []);
// Auto-fire screen() on route changes — App Router uses `usePathname()`.
useEffect(() => {
if (client && pathname) client.screen(pathname).catch(() => {});
}, [client, pathname]);
return <LayersContext.Provider value={client}>{children}</LayersContext.Provider>;
}
export function useLayers() {
return useContext(LayersContext);
}app/layout.tsx
import { LayersProvider } from './layers-provider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<LayersProvider>{children}</LayersProvider>
</body>
</html>
);
}Track events from a client component
'use client';
import { useLayers } from '../layers-provider';
export default function CheckoutPage() {
const layers = useLayers();
return (
<button
onClick={() => layers?.track('purchase_success', { revenue: 9.99, currency: 'USD' })}
>
Buy
</button>
);
}Pages Router (legacy)
Wrap _app.tsx and use next/router for the route-change hook:
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { LayersClient } from '@layers/client';
export default function App({ Component, pageProps }) {
const [client, setClient] = useState<LayersClient | null>(null);
const router = useRouter();
useEffect(() => {
const c = new LayersClient({
apiKey: 'unused',
appId: process.env.NEXT_PUBLIC_LAYERS_APP_ID!,
environment: process.env.NODE_ENV === 'production' ? 'production' : 'development',
});
c.init().then(() => setClient(c));
}, []);
useEffect(() => {
if (!client) return;
const onRouteChange = (url: string) => client.screen(url).catch(() => {});
router.events.on('routeChangeComplete', onRouteChange);
return () => router.events.off('routeChangeComplete', onRouteChange);
}, [client, router]);
return <Component {...pageProps} />;
}SSR caveats
- The SDK's event queue uses
localStoragefor persistence. Never initialize the client outsideuseEffector before'use client'boundary — Server Components / Edge runtime do not havewindow. - For server-side event firing (post-purchase webhooks, etc.), use
POST /v1/events— the partner-API forwarding surface — or@layers/noderunning in a Node server route. Do not import@layers/clientfromapp/api/*route handlers. - The
next/scriptstrategy isafterInteractiveby default — fine for Layers, since the SDK initializes on auseEffectafter hydration. If you need pre-hydration tracking (rare), usebeforeInteractiveand fire a manualtrack()once the client is ready.
Environment variables
| Var | Purpose | Build-time? |
|---|---|---|
NEXT_PUBLIC_LAYERS_APP_ID | The SDK app id from POST /sdk-apps. | Yes — must be a build-arg. Next.js inlines NEXT_PUBLIC_* at build, not runtime. |
NEXT_PUBLIC_LAYERS_INGEST_ENDPOINT | Override https://in.layers.com/events. Use for custom domain. | Yes. |
Verifying the install
After deploying, hit the active probe to confirm tracking is healthy:
curl -X POST https://api.layers.com/v1/projects/$PROJECT_ID/sdk-apps/$APP_ID/verify-tracking \
-H "Authorization: Bearer $LAYERS_API_KEY"Returns { status: "healthy" | "missing_events" | "schema_drift" | "no_install", … } plus per-check breakdown. See verify-tracking and SDK health.
Common pitfalls
window is not defined— you importedLayersClientoutside a client component. Move to a'use client'file or wrap inuseEffect.- Events fire but Meta sees zero — your
NEXT_PUBLIC_LAYERS_APP_IDis a build-arg. If you set it after the build, redeploy. - Screen events double-fire on Strict Mode in dev — React 18 Strict Mode mounts effects twice in development. The SDK dedupes the duplicate
screen()server-side; in production it's a no-op.