Layers

SDK — Next.js

Install `@layers/client` in a Next.js app. App Router and Pages Router patterns. SSR caveats. Auto-screen via `usePathname()`.

View as Markdown

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/client

App 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

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

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

app/checkout/page.tsx
'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:

pages/_app.tsx
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 localStorage for persistence. Never initialize the client outside useEffect or before 'use client' boundary — Server Components / Edge runtime do not have window.
  • For server-side event firing (post-purchase webhooks, etc.), use POST /v1/events — the partner-API forwarding surface — or @layers/node running in a Node server route. Do not import @layers/client from app/api/* route handlers.
  • The next/script strategy is afterInteractive by default — fine for Layers, since the SDK initializes on a useEffect after hydration. If you need pre-hydration tracking (rare), use beforeInteractive and fire a manual track() once the client is ready.

Environment variables

VarPurposeBuild-time?
NEXT_PUBLIC_LAYERS_APP_IDThe 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_ENDPOINTOverride 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 imported LayersClient outside a client component. Move to a 'use client' file or wrap in useEffect.
  • Events fire but Meta sees zero — your NEXT_PUBLIC_LAYERS_APP_ID is 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.

See also

On this page