Skip to content

Add a simple Next.js reference integration example#153

Open
niemyjski wants to merge 3 commits intomainfrom
feature/nextjs-reference-example
Open

Add a simple Next.js reference integration example#153
niemyjski wants to merge 3 commits intomainfrom
feature/nextjs-reference-example

Conversation

@niemyjski
Copy link
Member

Summary

This adds a small example/nextjs App Router reference app that shows an Exceptionless integration in a way that stays close to native Next.js conventions.

The example covers:

  • client startup in instrumentation-client.js
  • route transition logging via onRouterTransitionStart
  • client handled errors, unhandled rejections, and App Router error boundaries
  • server startup in instrumentation.js
  • uncaught server errors via onRequestError
  • explicit route-handler logging with request metadata and after()-based queue flushing
  • a small request adapter plus focused tests for the request-shape normalization

Why

We wanted a concrete Next.js example that helps answer two product questions:

  • what does an Exceptionless integration look like when it feels idiomatic to Next.js and Vercel developers?
  • how much framework-specific surface do we actually need before a dedicated @exceptionless/nextjs package is justified?

This PR deliberately keeps the example close to the framework file conventions and uses Exceptionless APIs directly in the integration points instead of layering on a large custom abstraction.

Implementation Notes

A few details are worth calling out explicitly:

  • The example uses @environment.data to attach Next.js-specific context like framework, router, runtime, and Vercel deployment metadata.
  • Request metadata is attached through a tiny adapter so the node client can enrich events from Next request shapes in a way that is similar to the existing Express flow.
  • The example currently imports the built SDK bundles from packages/browser/dist and packages/node/dist because the package entrypoints still re-export internal #/* imports that Next does not currently like in this setup.
  • The example runs next dev --webpack and next build --webpack for now. Turbopack still rejects the current node bundle path because of node-localstorage's dynamic require.
  • next.config.mjs contains a scoped webpack alias for source-map as an example-level workaround for the unused stacktrace-gps AMD branch. This keeps that workaround out of @exceptionless/browser itself.

Validation

  • npm test -- --project nextjs-example
  • npm run build --workspace=nextjs-example

Follow-ups

I think the main follow-up after this lands is not more example abstraction, but fixing the SDK packaging issues that forced the example to lean on built bundle imports and webpack mode. Once those are cleaned up, we can evaluate whether a thin @exceptionless/nextjs helper is still worth having.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a small App Router Next.js reference app under example/nextjs demonstrating an idiomatic Exceptionless integration across client + server hooks, with a tiny request adapter and tests.

Changes:

  • Add example/nextjs reference app (instrumentation hooks, error boundaries, route handler logging/flush, UI).
  • Add a Next.js request-shape adapter plus focused Vitest coverage.
  • Wire the new example into repo tooling (Vitest project, clean script, gitignore, lockfile).

Reviewed changes

Copilot reviewed 19 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
vitest.config.ts Adds a dedicated Vitest project for the Next.js example tests.
package.json Extends clean to remove Next.js .next build output.
package-lock.json Adds the Next.js example workspace and its dependency tree.
.gitignore Ignores the Next.js example .next/ directory.
example/nextjs/package.json Defines the nextjs-example workspace and its runtime dependencies/scripts.
example/nextjs/next.config.mjs Adds example-level webpack alias workaround for source-map.
example/nextjs/lib/next-request.js Implements request context normalization for Next request shapes.
example/nextjs/test/next-request.test.js Tests request context normalization for both web Request and onRequestError payload shape.
example/nextjs/lib/exceptionless-server.js Server-side Exceptionless startup + Next/Vercel environment enrichment plugin.
example/nextjs/lib/exceptionless-browser.js Browser-side Exceptionless startup + Next environment enrichment plugin.
example/nextjs/instrumentation.js Registers server instrumentation and submits events from onRequestError.
example/nextjs/instrumentation-client.js Starts browser client and logs router transitions via onRouterTransitionStart.
example/nextjs/components/ClientDemoPanel.jsx Client UI to trigger logs/errors and hit the demo route handler.
example/nextjs/app/page.jsx Home page describing integration coverage and rendering the demo panel.
example/nextjs/app/layout.jsx Root layout + metadata wiring and global CSS import.
example/nextjs/app/globals.css Styling for the demo UI.
example/nextjs/app/error.jsx Route-level App Router error boundary with Exceptionless capture for client-only errors.
example/nextjs/app/global-error.jsx Root-level App Router error boundary with Exceptionless capture for client-only errors.
example/nextjs/app/server-component-error/page.jsx Server-component error route to exercise onRequestError.
example/nextjs/app/api/demo/route.js Demo route handler logging + request metadata + after() queue flush.
example/nextjs/README.md Documentation for running and understanding the example integration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

await builder.submit();

after(async () => {
await Exceptionless.processQueue();
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The after() callback awaits Exceptionless.processQueue() without handling failures. If queue processing rejects, this can surface as an unhandled rejection in the Next.js runtime. Consider wrapping the flush in a try/catch (and optionally logging) so telemetry failures don’t destabilize the request lifecycle.

Suggested change
await Exceptionless.processQueue();
try {
await Exceptionless.processQueue();
} catch (error) {
// Prevent telemetry failures from causing unhandled promise rejections in Next.js
console.error("Failed to process Exceptionless queue in next/after():", error);
}

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +34
const { Exceptionless, KnownEventDataKeys, startup, toError } = await import("./lib/exceptionless-server.js");
const digest = typeof error === "object" && error !== null && "digest" in error ? error.digest : undefined;

await startup();

const builder = Exceptionless.createUnhandledException(toError(error), `nextjs.${context.routeType}`).addTags("on-request-error");

builder.setContextProperty(KnownEventDataKeys.RequestInfo, buildRequestContextFromOnRequestError(request));
builder.setProperty("digest", digest);
builder.setProperty("routePath", context.routePath);
builder.setProperty("routeType", context.routeType);
builder.setProperty("routerKind", context.routerKind);
builder.setProperty("renderSource", context.renderSource);
builder.setProperty("renderType", context.renderType);
builder.setProperty("revalidateReason", context.revalidateReason);

await builder.submit();
await Exceptionless.processQueue();
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onRequestError does multiple awaited SDK calls (startup(), submit(), processQueue()) without a protective try/catch. If any of these reject while handling an already-failing request, the hook can throw and potentially interfere with Next.js error handling. Wrap the reporting/flush flow in a try/catch and avoid rethrowing from this hook (log to console as a fallback).

Suggested change
const { Exceptionless, KnownEventDataKeys, startup, toError } = await import("./lib/exceptionless-server.js");
const digest = typeof error === "object" && error !== null && "digest" in error ? error.digest : undefined;
await startup();
const builder = Exceptionless.createUnhandledException(toError(error), `nextjs.${context.routeType}`).addTags("on-request-error");
builder.setContextProperty(KnownEventDataKeys.RequestInfo, buildRequestContextFromOnRequestError(request));
builder.setProperty("digest", digest);
builder.setProperty("routePath", context.routePath);
builder.setProperty("routeType", context.routeType);
builder.setProperty("routerKind", context.routerKind);
builder.setProperty("renderSource", context.renderSource);
builder.setProperty("renderType", context.renderType);
builder.setProperty("revalidateReason", context.revalidateReason);
await builder.submit();
await Exceptionless.processQueue();
try {
const { Exceptionless, KnownEventDataKeys, startup, toError } = await import("./lib/exceptionless-server.js");
const digest = typeof error === "object" && error !== null && "digest" in error ? error.digest : undefined;
await startup();
const builder = Exceptionless.createUnhandledException(toError(error), `nextjs.${context.routeType}`).addTags("on-request-error");
builder.setContextProperty(KnownEventDataKeys.RequestInfo, buildRequestContextFromOnRequestError(request));
builder.setProperty("digest", digest);
builder.setProperty("routePath", context.routePath);
builder.setProperty("routeType", context.routeType);
builder.setProperty("routerKind", context.routerKind);
builder.setProperty("renderSource", context.renderSource);
builder.setProperty("renderType", context.renderType);
builder.setProperty("revalidateReason", context.revalidateReason);
await builder.submit();
await Exceptionless.processQueue();
} catch (reportingError) {
// Avoid throwing from onRequestError so as not to interfere with Next.js error handling.
// Fallback to logging if the error reporting pipeline itself fails.
console.error("Failed to report request error via Exceptionless:", reportingError);
}

Copilot uses AI. Check for mistakes.
@niemyjski niemyjski force-pushed the feature/nextjs-reference-example branch from dd29ae9 to 08e97b7 Compare March 26, 2026 00:54
import { readdir, readFile, stat, writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";

const PACKAGE_IMPORT_PREFIX = "#/";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants