Skip to main content
@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

Five steps, each wired once. After this you don’t touch it again.
1

Install the package

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
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
4

Boot the client SDK

Next.js auto-loads instrumentation-client.ts in the browser on every page load. init() starts the SDK there — the provider only connects React to this running instance, so without it nothing is captured client-side. When INTERFERE_PUBLIC_KEY is set, the build fails if this file is missing.
instrumentation-client.ts
5

Add the provider

Connects React to the running SDK: the useInterfere hook, consent controls, and an error boundary that reports render errors. Wrap your app once, at the root layout.
app/layout.tsx
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

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.

Build plugin

withInterfere() is more than a config wrapper. At production build time it hooks Next.js’s runAfterProductionCompile to upload source maps, publish release metadata, and then delete the maps from the build output so your original source never ships. It also adjusts a few Next settings so symbolication and trace propagation work:
  • Forces productionBrowserSourceMaps: true whenever an API key is present. Set it to false and Interfere overrides it with a warning — without maps it can’t symbolicate.
  • Sets experimental.clientTraceMetadata (traceparent, baggage) so a server trace continues into the browser, and experimental.instrumentationHook on Next.js ≤ 14, which needs the flag to load instrumentation.ts.
  • Chains your hooks. An existing runAfterProductionCompile runs first; Interfere’s pipeline runs after it. A release-pipeline failure never fails the build — it warns and ships, and that release’s data is ingested but skipped when generating insights.
  • Guards your setup. The build fails fast if INTERFERE_PUBLIC_KEY is set but no instrumentation-client.ts exists, and warns if instrumentation.ts is missing. Setting interfere.buildId is rejected — override the commit SHA with INTERFERE_SOURCE_ID instead.
Keep withInterfere() the outermost wrapper. Its cleanup step runs last, so any other build plugin you compose runs — and uploads — before Interfere purges the maps.

Coexisting with other build tools

Sentry (@sentry/nextjs) and PostHog (@posthog/nextjs-config) read the same .next output and also delete maps after upload by default. When more than one tool uploads maps, the first to delete starves every later one. The rule: only the last tool to run deletes; every earlier one keeps its maps. Make Interfere the last tool to run, so it purges while Sentry keeps its maps:
next.config.ts
If another tool runs last instead, invert it: set sourceMaps: { purge: false } on Interfere and leave that tool’s deletion flag on. Un-symbolicated traces after a deploy mean an earlier tool deleted the maps too soon — flip the flags so the purging tool is genuinely last.
boolean
default:"true"
Passed as the first argument to withInterfere(settings, config). Deletes source maps from the build output after upload. Set to false when another tool shares the same .next build and runs its own upload afterward.

Release identity

Interfere derives each release from your commit SHA, so the client bundle and server spans resolve to the same release. It reads the SHA from your CI’s git env vars (VERCEL_GIT_COMMIT_SHA, GITHUB_SHA, …) or the local git checkout. If none is available — a Docker build with no git context, say — set INTERFERE_SOURCE_ID to the SHA on both the build and runtime environments.

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. Pass it to init() in your instrumentation-client.ts:
instrumentation-client.ts
string
default:"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:
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
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
object
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

React error boundary

The provider wraps your app in an error boundary that reports React render errors automatically. Opt out — to keep your own boundary as the only one, say — with errorBoundary={false}.
app/layout.tsx

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:
string
required
Your internal, stable user ID. Use this rather than the email.
object
required
Where the identity came from.
  • type: one of clerk, auth0, or custom
  • name: the provider’s display name, for example “Clerk”
string
Display name.
string
Email address.
string
Avatar URL.
object
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.
Need the current session id — to attach to a support ticket, say? Read it with the useSession hook from @interfere/next/provider.
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
Update consent at runtime through the same hook:
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.
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
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.

Custom spans

To time and trace your own work, wrap it in span. Interfere records the duration and outcome and links it into the surrounding trace.

Middleware and server actions

Next.js does not route proxy (middleware) or Server Action errors through onRequestError, so wrap them explicitly to capture them with full context.

Proxy (middleware)

Wrap your proxy.ts export (Next.js’s renamed middleware) with withInterfereProxy. It captures thrown errors — tagged with the request method and path — then re-throws them unchanged.
proxy.ts

Server actions

Wrap a Server Action with withInterfereServerAction to attribute failures to the action. It records the action’s name, an argument shape (types only, never values), and outcome on a span, then annotates any thrown error so Next’s onRequestError reports it as one event that groups with the client report — instead of forking a second, digest-less issue.

Other frameworks

Vite + React

@interfere/vite: a Vite plugin plus init() before render. Works for SPA and SSR.

TanStack Start

@interfere/vite: the same plugin, with server instrumentation for SSR.

NestJS

@interfere/nest: a backend module plus instrument.ts for server error capture.

FAQ

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.
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.
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.
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 }).