Repository: vercel/next.js
Pull request: Uninstall existing uncaughtException listeners to prevent the process from crashing

This broadens the scope of the `removeUnhandledRejectionListeners` flag to also remove `uncaughtException` listeners.

The motivation is the same: uncaught exceptions in one part of the page should not prevent the rest of the page from rendering.

See https://github.com/vercel/next.js/pull/77997 for more context.

I renamed the experimental flag to `removeUncaughtErrorAndRejectionListeners` to better reflect increased scope but the name isn't really that important since at this stage we just need to try out the behavior; it isn't documented anywhere.

Changed files:
filename: packages/next/src/server/config-schema.ts
patch: @@ -390,7 +390,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
         prerenderEarlyExit: z.boolean().optional(),
         proxyTimeout: z.number().gte(0).optional(),
         routerBFCache: z.boolean().optional(),
-        removeUnhandledRejectionListeners: z.boolean().optional(),
+        removeUncaughtErrorAndRejectionListeners: z.boolean().optional(),
         scrollRestoration: z.boolean().optional(),
         sri: z
           .object({

filename: packages/next/src/server/config-shared.ts
patch: @@ -510,14 +510,14 @@ export interface ExperimentalConfig {
   routerBFCache?: boolean
 
   /**
-   * Uninstalls all "unhandledRejection" listeners from the global process so
-   * that we can override the behavior, which in some runtimes is to exit the
-   * process on an unhandled rejection.
+   * Uninstalls all "unhandledRejection" and "uncaughtException" listeners from
+   * the global process so that we can override the behavior, which in some
+   * runtimes is to exit the process.
    *
    * This is experimental until we've considered the impact in various
    * deployment environments.
    */
-  removeUnhandledRejectionListeners?: boolean
+  removeUncaughtErrorAndRejectionListeners?: boolean
 
   serverActions?: {
     /**
@@ -1312,7 +1312,7 @@ export const defaultConfig: NextConfig = {
     useEarlyImport: false,
     viewTransition: false,
     routerBFCache: false,
-    removeUnhandledRejectionListeners: false,
+    removeUncaughtErrorAndRejectionListeners: false,
     staleTimes: {
       dynamic: 0,
       static: 300,

filename: packages/next/src/server/next-server.ts
patch: @@ -161,7 +161,7 @@ function getMiddlewareMatcher(
 }
 
 function installProcessErrorHandlers(
-  shouldRemoveUnhandledRejectionListeners: boolean
+  shouldRemoveUncaughtErrorAndRejectionListeners: boolean
 ) {
   // The conventional wisdom of Node.js and other runtimes is to treat
   // unhandled errors as fatal and exit the process.
@@ -199,11 +199,12 @@ function installProcessErrorHandlers(
   // So, we're going to intentionally override the default error handling
   // behavior of the outer JS runtime to be more forgiving
 
-  // Remove any existing "unhandledRejection" handlers. This is gated behind
-  // an experimental flag until we've considered the impact in various
-  // deployment environments. It's possible this may always need to
+  // Remove any existing "unhandledRejection" and "uncaughtException" handlers.
+  // This is gated behind an experimental flag until we've considered the impact
+  // in various deployment environments. It's possible this may always need to
   // be configurable.
-  if (shouldRemoveUnhandledRejectionListeners) {
+  if (shouldRemoveUncaughtErrorAndRejectionListeners) {
+    process.removeAllListeners('uncaughtException')
     process.removeAllListeners('unhandledRejection')
   }
 
@@ -370,10 +371,10 @@ export default class NextNodeServer extends BaseServer<
       populateStaticEnv(this.nextConfig)
     }
 
-    const shouldRemoveUnhandledRejectionListeners = Boolean(
-      options.conf.experimental?.removeUnhandledRejectionListeners
+    const shouldRemoveUncaughtErrorAndRejectionListeners = Boolean(
+      options.conf.experimental?.removeUncaughtErrorAndRejectionListeners
     )
-    installProcessErrorHandlers(shouldRemoveUnhandledRejectionListeners)
+    installProcessErrorHandlers(shouldRemoveUncaughtErrorAndRejectionListeners)
   }
 
   public async unstable_preloadEntries(): Promise<void> {