Handling errors in the Next.js App Router is fundamentally different from the old Pages directory.
With React Server Components (RSC), Client Boundaries, Server Actions, and streaming SSR, a single uncaught exception can happen in three different runtimes: on the server, at the edge, or directly in the user's browser.
If you rely on traditional enterprise monitoring tools (like Sentry or Datadog), you quickly run into two major developer headaches:
- Bundle Weight: Heavy SDKs add 100KB+ of minified code to your frontend bundle, negatively impacting First Contentful Paint (FCP) and Google Core Web Vitals [1.2.2].
- The Re-Render Storm: A broken React client component can trigger an infinite re-render loop, firing hundreds of duplicate HTTP error reports in seconds and flooding your notification channels [1.1.7].
Here is an architectural breakdown of how to handle errors cleanly in Next.js App Router with under 5KB overhead, on-device privacy, and zero alert spam.
1. The Right Way to Intercept Errors in App Router
Next.js provides error.tsx and global-error.tsx boundaries, but they only catch rendering errors inside React trees. They do not catch:
- Static resource failures (failing CDN scripts or images).
- Asynchronous errors in third-party scripts.
- Unhandled asynchronous Promise rejections (
window.onunhandledrejection).
To catch everything without delaying page hydration, the best approach is to register lightweight native listeners before interactive scripts load:
javascript
// Lightweight browser interceptor
window.addEventListener('error', (event) => {
reportCrash({
message: event.message,
stack: event.error?.stack || `${event.filename}:${event.lineno}`,
url: window.location.href,
});
});
window.addEventListener('unhandledrejection', (event) => {
reportCrash({
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
url: window.location.href,
});
});
Top comments (0)