ErrTap / DevelopersDocumentation
ErrTap docsplatforms / nextjs

Next.js SDK

Install @errtap/next and capture errors on both sides of a Next.js app — the server via instrumentation.ts, the browser via instrumentation-client.ts.

Install

npm i @errtap/next

@errtap/next is two thin wrappers: @errtap/next/server wraps @errtap/node, and @errtap/next/client wraps @errtap/browser. Initialise both to cover Server Components, Route Handlers, and everything that runs in the browser.

Server — instrumentation.ts

Next.js calls register() on boot for both the Node.js and Edge runtimes. Edge exposes a partial process without process.on, so only init on Node and load the server SDK with a dynamic import:

// instrumentation.ts
export async function register() {
  // Edge has no process.on — skip Node SDK there
  if (process.env.NEXT_RUNTIME !== 'nodejs') return;
  if (!process.env.ERRTAP_DSN) return;

  const { init } = await import('@errtap/next/server');
  init({
    dsn: process.env.ERRTAP_DSN, // https://et_…@host
    environment: process.env.NODE_ENV,
    release: process.env.VERCEL_GIT_COMMIT_SHA,
  });
}

export async function onRequestError(error, request, context) {
  if (process.env.NEXT_RUNTIME !== 'nodejs' || !process.env.ERRTAP_DSN) return;
  const { onRequestError: captureRequestError } = await import('@errtap/next/server');
  await captureRequestError(error, request, context);
}

onRequestError is Next's request-error hook for Server Components, Route Handlers, and Server Actions. @errtap/next/server excludes headers from the event and sets exitOnFatal: false so Next owns the process.

Browser — instrumentation-client.ts

Requires Next.js 15.3+. The file runs before your app hydrates:

// instrumentation-client.ts
import { init } from '@errtap/next/client';

if (process.env.NEXT_PUBLIC_ERRTAP_DSN) {
  init({
    dsn: process.env.NEXT_PUBLIC_ERRTAP_DSN,
    environment: process.env.NODE_ENV,
    // Must match the release your sourcemaps are uploaded under (see Sourcemaps below).
    release: process.env.NEXT_PUBLIC_ERRTAP_RELEASE,
  });
}

The browser side needs a NEXT_PUBLIC_-prefixed env var — DSN keys are write-only, so exposing one to the client is by design.

Sourcemaps (de-minify browser stack traces)

Production browser bundles are minified, so raw stack traces read like rD@…/chunks/abc.js:1:35060. ErrTap de-minifies them at ingest when it has the release's sourcemaps — but only if three things line up:

  1. The build emits sourcemaps. Turn them on in next.config.mjs:

    const nextConfig = {
      productionBrowserSourceMaps: true,
    };
  2. The maps are uploaded under a release. @errtap/next ships an uploader that posts every .js.map to /ingest/sourcemaps and then deletes the maps from the build so your source isn't served publicly. Run it after next build:

    // package.json
    "scripts": {
      "build": "next build",
      "postbuild": "errtap-upload-sourcemaps"
    }
  3. The event and the maps share the same release. Set one release value for both the client SDK and the uploader. On Vercel:

    NEXT_PUBLIC_ERRTAP_RELEASE=$VERCEL_GIT_COMMIT_SHA   # read by init() above
    ERRTAP_DSN=https://et_…@your-host                  # used by the uploader

    The uploader reads ERRTAP_RELEASE, then NEXT_PUBLIC_ERRTAP_RELEASE, then VERCEL_GIT_COMMIT_SHA. Browser instrumentation also falls back to Vercel's automatic NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA, so no manual release variable is required on Vercel. If the DSN or release is missing, the postbuild still deletes generated maps instead of leaving application source publicly accessible; it logs that upload was skipped.

Miss any one and frames stay minified: no release on the event skips symbolication entirely, and a release with no uploaded maps has nothing to apply. Server errors (instrumentation.ts) use the same mechanism — tag them with the same release.

Safari and Firefox report frames as fn@url:line:col (no at); ErrTap symbolicates those too, so mobile-Safari errors de-minify like Chrome's.

Env

VariableWhereNotes
ERRTAP_DSNServerURL DSN or bare key for Node instrumentation + sourcemap upload
NEXT_PUBLIC_ERRTAP_DSNBrowserSame or separate project DSN for the client SDK
NEXT_PUBLIC_ERRTAP_RELEASEBrowser + buildRelease tag; must match on the event and the uploaded sourcemaps

Keep real DSNs in .env.local (gitignored), not in source.

Manual capture

Import from the side you're on:

import { captureException, captureMessage, logger } from '@errtap/next/server';
// or '@errtap/next/client' in Client Components

logger.info('checkout started', { cartId });

try {
  await chargeCard();
} catch (err) {
  captureException(err as Error, { tags: { flow: 'checkout' } });
  throw err;
}

Verify

Ingest returns HTTP 202 when accepted (not a sync DB write):

curl -i -X POST https://api.errtap.com/ingest/error \
  -H "Authorization: DSN et_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{"message":"test event","type":"Error","environment":"production"}'

Then trigger a real error once through the app so the SDK path is exercised. Events show under Issues.

Options

FieldTypeDescription
dsnstringURL DSN (https://et_…@host) or bare key (requires endpoint)
endpointstringOverride ingest URL; optional when dsn is a URL DSN
environmentstringe.g. production, preview
releasestringTies errors to a deploy — pair with release tracking
tagsRecord<string, unknown>Attached to every event

On this page