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:
| Signal | Details |
|---|---|
next dependency | Version is read from package.json |
next.config.js / .ts / .mjs / .cjs | Works even if dependency parsing is unusual |
Then it disambiguates router type:
| Router signal | Result |
|---|---|
app/ or src/app/ only | App Router |
pages/ or src/pages/ only | Pages Router |
| Both or neither | Wizard 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-jsEnvironment variables
NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY=your-api-key
NEXT_PUBLIC_HUMANBEHAVIOR_INGESTION_URL=https://ingest.humanbehavior.coNEXT_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.
App Router: recommended leaf initializer
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.
| Pattern | App Router status |
|---|---|
Leaf "use client" component returning null | Recommended |
HumanBehaviorProvider around all children in app/layout.tsx | Avoid |
Direct HumanBehaviorTracker.init in a Server Component | Wrong |
Init in middleware.ts, route handlers, or server actions | Wrong |
Options worth setting in Next.js
| Option | Why |
|---|---|
release | Lets Releases group errors by deploy |
environment | Distinguishes production/staging error streams |
commitSha | Enables source links for issues when paired with source maps |
dist | Build discriminator for source-map upload matching |
captureRequestBodies | Opt-in only; request/response bodies are redacted but still sensitive |
redactionStrategy | Controls 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
| Setup | Ingestion URL |
|---|---|
| Hosted HumanBehavior | Leave unset, default hosted ingestion is used |
| Local full stack | Point at local ingestion, commonly http://localhost:8000 |
| Reverse proxy | Point 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:
- Set
releasein SDK init. - Build the app.
- Upload
.mapfiles with the wizard source-map command or your CI wrapper.
See Source maps.
Common pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
API key lacks NEXT_PUBLIC_ | No init request | Rename env var and rebuild/restart |
| SDK import in a Server Component | Build/runtime server errors | Move all SDK imports into "use client" files |
| Root provider wrapper | Hydration mismatches | Use leaf initializer |
| App and Pages routers both present | Wizard asks for router | Choose the router serving your app shell |
| Env changed on Vercel but app not redeployed | Old key still used | Redeploy |
Verify
- Start Next locally or deploy a preview.
- Open the browser Network tab.
- Confirm
/api/ingestion/eventsand/heartbeatreturn 2xx (init is not required). - Navigate between routes; Next client navigation should emit page activity.
- Open Replays and Dashboard.
Full checklist: Verify data is flowing.