Next.js 15+ exposes an instrumentation hook that intercepts unhandled server-side errors before the framework returns a generic error response. By exporting an onRequestError function from your instrumentation.js (or .ts) file, you can forward these errors to Atatus with full request and routing context.

Why This Is Required

Next.js catches errors thrown during Server Component rendering, Route Handlers, and Server Actions internally, and serves a generic error page. Because the framework handles these errors before they propagate, the Atatus agent cannot capture them through standard auto-instrumentation alone. The onRequestError hook provides a global entry point to intercept and report these errors.

Setup

Create an instrumentation.js (or instrumentation.ts) file in your project root (or inside src/ if your project uses the src layout).

JavaScript

copy
icon/buttons/copy
/** @type {import('next').Instrumentation['onRequestError']} */
export async function onRequestError(err, request, context) {
  if (process.env.NEXT_RUNTIME !== "nodejs") {
    return;
  }

  const atatus = require("atatus-nodejs");

  atatus.notifyError(
    err instanceof Error ? err : new Error(String(err)),
    {
      handled: false,
      customData: {
        source: "next-on-request-error",
        request,
        context,
        digest:
          err &&
          typeof err === "object" &&
          "digest" in err
            ? String(err.digest)
            : undefined,
      },
    }
  );
}

TypeScript

copy
icon/buttons/copy
import type { Instrumentation } from "next";

type AtatusAgent = typeof import("atatus-nodejs");

function getDigest(err: unknown) {
  if (typeof err === "object" && err !== null && "digest" in err) {
    return String(err.digest);
  }
  return undefined;
}

function normalizeError(err: unknown) {
  return err instanceof Error ? err : new Error(String(err));
}

export const onRequestError: Instrumentation.onRequestError = async (
  err,
  request,
  context
) => {
  if (process.env.NEXT_RUNTIME !== "nodejs") {
    return;
  }

  const atatus = require("atatus-nodejs") as AtatusAgent;

  atatus.notifyError(normalizeError(err), {
    handled: false,
    customData: {
      source: "next-on-request-error",
      request,
      context,
      digest: getDigest(err),
    },
  });
};

How It Works

When an unhandled error occurs during server-side rendering or routing, Next.js invokes onRequestError. The function:

  1. Guards against non-Node.js runtimes (Edge Runtime is not supported).
  2. Dynamically requires atatus-nodejs to avoid bundler conflicts.
  3. Reports the error via atatus.notifyError with request metadata and the Next.js error digest.

Reported errors appear under Exceptions in your Atatus dashboard with full stack traces.

Troubleshooting

If unhandled server-side errors are not appearing after setup:

  • Incorrect file location: Place instrumentation.js / instrumentation.ts in the project root or inside src/ (adjacent to app or pages). It must not be placed inside app/ or pages/.
  • Missing export: The function must be exported — export async function onRequestError.
  • Server not restarted: Next.js compiles the instrumentation file at startup. You must restart the dev or production server after any changes.
  • Next.js 14 — experimental flag required: Enable the instrumentation hook explicitly in next.config.js: javascript const nextConfig = { experimental: { instrumentationHook: true, }, };

Exclude Packages from Bundling

Next.js bundles server-side dependencies by default. The Atatus agent instruments packages by intercepting Node.js require() calls at runtime — this only works when packages are loaded natively, not compiled into the bundle.

Add atatus-nodejs and any database drivers you use to serverExternalPackages in next.config.js:

copy
icon/buttons/copy
/** @type {import('next').NextConfig} */
const nextConfig = {
  serverExternalPackages: ["atatus-nodejs", "mongodb", "pg", "redis", "mysql2", "mongoose"],
};

module.exports = nextConfig;

Include only the packages your project actually uses. This ensures they are resolved via Node.js require() at runtime, enabling the Atatus agent to instrument database queries, external HTTP calls, and other operations.