Add a simple Next.js reference integration example#153
Add a simple Next.js reference integration example#153
Conversation
There was a problem hiding this comment.
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/nextjsreference 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(); |
There was a problem hiding this comment.
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.
| 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); | |
| } |
example/nextjs/instrumentation.js
Outdated
| 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(); |
There was a problem hiding this comment.
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).
| 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); | |
| } |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dd29ae9 to
08e97b7
Compare
Summary
This adds a small
example/nextjsApp Router reference app that shows an Exceptionless integration in a way that stays close to native Next.js conventions.The example covers:
instrumentation-client.jsonRouterTransitionStartinstrumentation.jsonRequestErrorafter()-based queue flushingWhy
We wanted a concrete Next.js example that helps answer two product questions:
@exceptionless/nextjspackage 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:
@environment.datato attach Next.js-specific context like framework, router, runtime, and Vercel deployment metadata.packages/browser/distandpackages/node/distbecause the package entrypoints still re-export internal#/*imports that Next does not currently like in this setup.next dev --webpackandnext build --webpackfor now. Turbopack still rejects the current node bundle path because ofnode-localstorage's dynamicrequire.next.config.mjscontains a scoped webpack alias forsource-mapas an example-level workaround for the unusedstacktrace-gpsAMD branch. This keeps that workaround out of@exceptionless/browseritself.Validation
npm test -- --project nextjs-examplenpm run build --workspace=nextjs-exampleFollow-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/nextjshelper is still worth having.