Developer pages

Next.js

Install HumanBehavior in Next.js App Router or Pages Router without hydration mismatches.

Next.js is the framework where placement matters most. The shipped guidance is a client-only leaf initializer that returns null, rendered from the layout/app shell, rather than wrapping the root tree with a provider.

Wizard detection

The wizard detects Next.js when it sees either:

SignalDetails
next dependencyVersion is read from package.json
next.config.js / .ts / .mjs / .cjsWorks even if dependency parsing is unusual

Then it disambiguates router type:

Router signalResult
app/ or src/app/ onlyApp Router
pages/ or src/pages/ onlyPages Router
Both or neitherWizard asks: Which router are you using? with App Router / Pages Router

If the wizard cannot decide, it defaults context toward App Router after the setup question path.

Package

pnpm add humanbehavior-js

Environment variables

NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY=your-api-key
NEXT_PUBLIC_HUMANBEHAVIOR_INGESTION_URL=https://ingest.humanbehavior.co

NEXT_PUBLIC_ is required because the tracker runs in the browser. A non-public HUMANBEHAVIOR_API_KEY will be invisible to the client bundle.

The ingestion URL is optional unless you run a local ingestion server or reverse proxy.

Create a client component such as app/HumanBehaviorInit.tsx:

"use client";

import { useEffect } from "react";
import { HumanBehaviorTracker } from "humanbehavior-js";

export function HumanBehaviorInit() {
  useEffect(() => {
    const apiKey = process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY;
    if (!apiKey) return;
    HumanBehaviorTracker.init(apiKey, {
      ingestionUrl: process.env.NEXT_PUBLIC_HUMANBEHAVIOR_INGESTION_URL,
    });
  }, []);
  return null;
}

Then render it once inside app/layout.tsx:

import React, { type ReactNode } from "react";
import { HumanBehaviorInit } from "./HumanBehaviorInit";

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <HumanBehaviorInit />
        {children}
      </body>
    </html>
  );
}

The layout stays a Server Component. The browser SDK never imports into server code because the initializer file starts with "use client" and calls init inside useEffect.

Pages Router pattern

For Pages Router, initialize from _app.tsx with a small client component or effect:

import type { AppProps } from "next/app";
import { useEffect } from "react";
import { HumanBehaviorTracker } from "humanbehavior-js";

function HumanBehaviorInit() {
  useEffect(() => {
    const apiKey = process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY;
    if (!apiKey) return;

    HumanBehaviorTracker.init(apiKey, {
      ingestionUrl: process.env.NEXT_PUBLIC_HUMANBEHAVIOR_INGESTION_URL,
    });
  }, []);

  return null;
}

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <HumanBehaviorInit />
      <Component {...pageProps} />
    </>
  );
}

If your Pages app already has providers, render the initializer alongside them. It does not need to wrap children.

Why not wrap the App Router root provider?

The React provider is shipped, but App Router roots are hydration-sensitive. Wrapping the entire root can change generated attributes from UI libraries that use React useId, producing hydration warnings that are hard to distinguish from real app bugs.

PatternApp Router status
Leaf "use client" component returning nullRecommended
HumanBehaviorProvider around all children in app/layout.tsxAvoid
Direct HumanBehaviorTracker.init in a Server ComponentWrong
Init in middleware.ts, route handlers, or server actionsWrong

Options worth setting in Next.js

OptionWhy
releaseLets Releases group errors by deploy
environmentDistinguishes production/staging error streams
commitShaEnables source links for issues when paired with source maps
distBuild discriminator for source-map upload matching
captureRequestBodiesOpt-in only; request/response bodies are redacted but still sensitive
redactionStrategyControls replay masking in rendered pages

Example:

HumanBehaviorTracker.init(apiKey, {
  ingestionUrl: process.env.NEXT_PUBLIC_HUMANBEHAVIOR_INGESTION_URL,
  release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
  environment: process.env.NEXT_PUBLIC_VERCEL_ENV,
  commitSha: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
});

Only use env vars that are intentionally public. Do not expose private server secrets just to fill optional SDK metadata.

Local development

SetupIngestion URL
Hosted HumanBehaviorLeave unset, default hosted ingestion is used
Local full stackPoint at local ingestion, commonly http://localhost:8000
Reverse proxyPoint at your proxy origin/path if it forwards /api/ingestion/*

If you point the SDK at the Next dashboard app without a proxy, ingestion requests will fail.

Source maps

For readable production stacks:

  1. Set release in SDK init.
  2. Build the app.
  3. Upload .map files with the wizard source-map command or your CI wrapper.

See Source maps.

Common pitfalls

PitfallSymptomFix
API key lacks NEXT_PUBLIC_No init requestRename env var and rebuild/restart
SDK import in a Server ComponentBuild/runtime server errorsMove all SDK imports into "use client" files
Root provider wrapperHydration mismatchesUse leaf initializer
App and Pages routers both presentWizard asks for routerChoose the router serving your app shell
Env changed on Vercel but app not redeployedOld key still usedRedeploy

Verify

  1. Start Next locally or deploy a preview.
  2. Open the browser Network tab.
  3. Confirm /api/ingestion/events and /heartbeat return 2xx (init is not required).
  4. Navigate between routes; Next client navigation should emit page activity.
  5. Open Replays and Dashboard.

Full checklist: Verify data is flowing.