Agent-readable docs index: /docs/llms.txt. Full docs in one file: /docs/llms-full.txt. Download /docs/docs.zip to grep all markdown files locally.

Next.js

@interfere/next connects your Next.js app to Interfere. It's a one-time install: add a few lines, ship as usual, and Interfere captures everything your app emits (errors, sessions, traces, and logs) with no sampling and nothing to tune. From there, Interfere groups related symptoms into a single problem, decides how much it matters, and investigates the cause for you.
This page covers the install and the handful of settings most teams actually touch: naming your app, identifying your users, and respecting consent.
You'll need a surface public key (interfere_pub_<region>_…) from Surfaces. For source-map upload and release tracking you'll also want an Interfere API key (interfere_secret_<region>_…). See Environment variables.

Prerequisites

  • Next.js >= 16 (App Router)
  • React >= 19
  • Node.js >= 20

Quick start

Four steps, each wired once. After this you don't touch it again.
  1. Install the package
    npm install @interfere/next
  2. Wrap your Next.js config
    This lets Interfere upload source maps at build time, so a production stack trace points back to your original code instead of minified output. It also tags each build as a release.
    next.config.ts
    import { withInterfere } from "@interfere/next/config"; import type { NextConfig } from "next"; const config: NextConfig = {}; export default withInterfere(config);
  3. Wire server instrumentation
    So server-side errors get captured and OTel bootstraps for Server Components, route handlers, and server actions. Next.js calls the exported register() on server start.
    instrumentation.ts
    export { onRequestError, register } from "@interfere/next/instrumentation";
  4. Add the provider
    Starts the SDK in the browser and begins capturing. Wrap your app once, at the root layout.
    app/layout.tsx
    import { InterfereProvider } from "@interfere/next/provider"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <InterfereProvider>{children}</InterfereProvider> </body> </html> ); }
That's the whole install. Run your app, trigger an error, and it shows up in your dashboard within seconds, already grouped and triaged. Open your workspace to confirm it. There's nothing else to configure. (The SDK stays quiet outside production; see the FAQ to capture in development.)

Environment variables

VariableRequiredDescription
INTERFERE_PUBLIC_KEYYesYour surface public key (interfere_pub_<region>_…, where <region> is us or eu). Safe to expose; it only authorizes ingestion.
INTERFERE_API_KEYFor releasesInterfere API key (interfere_secret_<region>_…). A server-side build credential for source-map upload and release metadata. Never expose it in browser code.
INTERFERE_ENVIRONMENTNoLabels this deployment's telemetry and releases (production, staging, preview, or a label you choose). Read at build time and stamped into the bundle, so it needs no NEXT_PUBLIC_ prefix. Defaults to VERCEL_ENV, then NODE_ENV, then development. See Environments.
INTERFERE_API_URLNoOverride the ingest endpoint (for example, to target a regional endpoint). Defaults to Interfere's endpoint. See Custom ingest domain.
INTERFERE_API_KEY is a secret. Keep it in server and CI environments only. The public key (INTERFERE_PUBLIC_KEY) is the only key that reaches the browser.

Configuration

Interfere is built to run without tuning, so most apps install it and stop here. Reach for these only when you have a specific reason.

Name your app

If you run more than one app against Interfere (a storefront and an admin panel, say), give each a serviceName so problems, sessions, and metrics are attributed to the right one. Set it in an optional instrumentation-client.ts:
instrumentation-client.ts
import { init } from "@interfere/next/instrument-client"; init({ serviceName: "@acme/storefront" });
path serviceNamestringdefault: interfere-sdk
A stable name for this app. Interfere uses it to keep each surface's data separate, and to correlate the same issue across surfaces into one problem.

Environments

Interfere tags everything it captures with the environment it ran in, and labels each release the same way. In the dashboard, releases carry an environment badge and you can filter by environment, so production data and preview data stay apart.
Set it with INTERFERE_ENVIRONMENT:
INTERFERE_ENVIRONMENT=production
The label is free-form: production, staging, preview, canary, or whatever you name it. If you don't set it, Interfere falls back to VERCEL_ENV, then NODE_ENV, then development. On Vercel you usually get the right value without doing anything.
Interfere reads this at build time and bakes it into your bundle, so it does not need a NEXT_PUBLIC_ prefix.
Interfere drops local-like environments (development, local, test) before ingestion, so everyday local traffic never reaches your dashboard. An app with no environment set defaults to development and is filtered out, which is one reason nothing shows up until you deploy. Set a real environment, or turn off environment filtering for the surface, to capture from one.

Custom ingest domain

Interfere's public endpoint is the default. Point the browser SDK at a subdomain of your own site to survive ad-blocker deny-lists (no server component involved), or at a regional endpoint:
next.config.ts
import { withInterfere } from "@interfere/next/config"; import type { NextConfig } from "next"; const config: NextConfig = {}; export default withInterfere({ interfere: { apiHost: "https://in.yourapp.com" }, ...config, });
Add a CNAME pointing your subdomain at the target shown in your dashboard; Interfere provisions TLS automatically. To target a regional endpoint instead, set apiHost (or the INTERFERE_API_URL env var) to that URL. Ingest requests never carry cookies.

Choose what's captured

By default Interfere captures all of the signals below. Turn any off with the provider's plugins prop. For example, disable session replay if you don't want recordings:
app/layout.tsx
<InterfereProvider plugins={{ replay: false }}>{children}</InterfereProvider>
path pluginsobject
Each signal can be toggled on or off. All default to on.
  • errors: uncaught exceptions
  • logs: console output
  • device: device and browser info
  • pageEvents: pageviews and clicks
  • rageClick: rage-click detection
  • replay: session replay

Identity

By default a session is anonymous. Link it to your authenticated user so a problem shows you who hit it in Users, with a name and email instead of an opaque id. Call identity.set() from the useInterfere hook once your user loads:
import { useInterfere } from "@interfere/next/provider"; function SyncIdentity() { const { identity } = useInterfere(); const { user } = useAuthProvider(); // Clerk, Auth0, etc. useEffect(() => { if (!user) return; identity.set({ identifier: user.id, name: user.name, email: user.email, source: { type: "clerk", name: "Clerk" }, }); }, [user]); return null; }
path identifierstringrequired
Your internal, stable user ID. Use this rather than the email.
path sourceobjectrequired
Where the identity came from.
  • type: one of clerk, auth0, or custom
  • name: the provider's display name, for example "Clerk"
path namestring
Display name.
path emailstring
Email address.
path avatarstring
Avatar URL.
path traitsobject
Any extra metadata you want attached to the user (Record<string, unknown>).
identity.set() is deduplicated per session, so calling it on every render is fine. Identity clears automatically when the session rotates.

Consent

By default all features are active. To respect a cookie banner or privacy preference, pass consent to the provider. Once you do, only essential capture (error tracking and logs) plus the categories you opt into will run:
app/layout.tsx
<InterfereProvider consent={{ analytics: true, replay: false }}>{children}</InterfereProvider>
CategoryWhat it coversGateable
necessaryError tracking, logsAlways on
analyticsPage events, rage clicks, device infoYes
replaySession replayYes
Update consent at runtime through the same hook:
const { consent } = useInterfere(); consent.set({ analytics: true, replay: true }); // selective consent.set(); // grant everything
Interfere works with any consent platform (c15t, CookieYes, OneTrust). Map its booleans to the categories above.
Set the consent prop from the provider's first render so a non-consented feature never gets a chance to load. Update it through the hook as the user changes their mind.

Mask replay data

Session replay records the DOM, so anything on screen can land in a recording. Password inputs are masked for you, and three privacy classes are always active — they're hard-wired, so no config can turn them off. Add a class to any element to redact more; nothing else to set up.
ClassEffect
interfere-blockThe element isn't recorded. It replays as a same-size placeholder. Drops a whole input, widget, or region.
interfere-ignoreThe element still renders, but its input events aren't recorded — keystrokes and changes are never captured.
interfere-maskThe element's text and its children's text is masked. Applies to rendered text, not input values.
<input className="interfere-block" /> {/* whole element dropped */} <input className="interfere-ignore" /> {/* input events not recorded */} <span className="interfere-mask">$128,400.00</span> {/* text + children masked */}
To mask input values by type, pass a replay config to the provider's plugins. Only passwords are masked by default; set maskAllInputs: true for a stricter baseline that redacts every input.
app/layout.tsx
<InterfereProvider plugins={{ replay: { maskInputOptions: { email: true }, // mask specific input types // maskAllInputs: true, // or mask every input value }, }} > {children} </InterfereProvider>
For finer control, plugins.replay also forwards raw rrweb options — maskTextSelector / blockSelector / ignoreSelector to target elements by CSS selector, maskInputFn / maskTextFn to transform masked values, and recordCanvas. These are additive; the interfere-* classes still apply.

Report a handled error

Interfere captures uncaught errors for you. When you catch an error yourself but still want it reported, call capture on the client or captureError on the server.
import { capture } from "@interfere/next/instrument-client"; try { await saveDraft(); } catch (error) { capture(error); }
import { captureError } from "@interfere/next/server"; export async function placeOrder() { try { await chargeCard(); } catch (error) { captureError(error); throw error; } }

FAQ

Why isn't anything showing up in development?
The SDK stays quiet when NODE_ENV !== "production", so local noise doesn't reach your dashboard. To capture while testing, call init({ enabled: true }) in instrumentation-client.ts. Interfere also drops the development, local, and test environments before ingestion, so set a real environment too if you want that data to land.
Do you handle the same issue across multiple apps?
Yes. Give each app its own serviceName. When the same issue hits more than one surface, Interfere correlates it into a single problem instead of a separate alert per app.
What about ad-blockers?
Set apiHost to a subdomain of your own site — a CNAME to the target shown in your dashboard. Telemetry then posts to your own origin, no server route or middleware needed.
Can I ship errors only, without analytics or replay?
Yes. Disable the signals you don't want with the provider's plugins prop, for example <InterfereProvider plugins={{ replay: false, pageEvents: false }}>. To drop browser tracing from the bundle entirely, pass init({ tracing: false }).