Repository: vercel/next.js
Pull request: Improve no response route handler error

## Improve route handler "no response" error message for debugging

### What?

Improves the error message thrown when an app route handler doesn't return a `Response` object to include additional debugging information:

- **The actual type received** - Shows whether it was `null`, `undefined`, or the constructor name for objects
- **The HTTP method** - e.g., `GET`, `POST`  
- **The URL pathname** - The route being requested

### Why?

When debugging intermittent prerender failures like:
```
Error: No response is returned from route handler '[project]/apps/web/app/favicon--route-entry.js'.
```

It was unclear what the handler actually returned. This made it difficult to diagnose whether:
- The handler returned `undefined` (didn't return anything)
- The handler returned `null` (explicit null)
- The handler returned some other object type (module loading issue)

### How?

Updated the error message format from:
```
No response is returned from route handler '/path/route.ts'. Ensure you return a `Response` or a `NextResponse` in all branches of your handler.
```

To:
```
No response is returned from route handler '/path/route.ts'. Expected a Response object but received 'undefined' (method: GET, url: /favicon.ico). Ensure you return a `Response` or a `NextResponse` in all branches of your handler.
```

### Changes

- `packages/next/src/server/route-modules/app-route/module.ts` - Enhanced error with type detection and request context
- `packages/next/errors.json` - Updated error template
- `test/e2e/app-dir/app-routes/app-custom-routes.test.ts` - Updated test to match new error format

Changed files:
filename: packages/next/errors.json
patch: @@ -982,5 +982,6 @@
   "981": "resolvedPathname must be set in request metadata",
   "982": "`serializeResumeDataCache` should not be called in edge runtime.",
   "983": "Invariant: global-error module is required but not found in loader tree",
-  "984": "LRUCache: calculateSize returned %s, but size must be > 0. Items with size 0 would never be evicted, causing unbounded cache growth."
+  "984": "LRUCache: calculateSize returned %s, but size must be > 0. Items with size 0 would never be evicted, causing unbounded cache growth.",
+  "985": "No response is returned from route handler '%s'. Expected a Response object but received '%s' (method: %s, url: %s). Ensure you return a \\`Response\\` or a \\`NextResponse\\` in all branches of your handler."
 }

filename: packages/next/src/server/route-modules/app-route/module.ts
patch: @@ -647,8 +647,19 @@ export class AppRouteRouteModule extends RouteModule<
 
     // Validate that the response is a valid response object.
     if (!(res instanceof Response)) {
+      const invalidType =
+        res === null
+          ? 'null'
+          : res === undefined
+            ? 'undefined'
+            : typeof res === 'object'
+              ? res.constructor?.name || 'object'
+              : typeof res
+
       throw new Error(
-        `No response is returned from route handler '${this.resolvedPagePath}'. Ensure you return a \`Response\` or a \`NextResponse\` in all branches of your handler.`
+        `No response is returned from route handler '${this.resolvedPagePath}'. ` +
+          `Expected a Response object but received '${invalidType}' (method: ${request.method}, url: ${requestStore.url.pathname}). ` +
+          `Ensure you return a \`Response\` or a \`NextResponse\` in all branches of your handler.`
       )
     }
 

filename: test/e2e/app-dir/app-routes/app-custom-routes.test.ts
patch: @@ -523,14 +523,16 @@ describe('app-custom-routes', () => {
   })
 
   describe('error conditions', () => {
-    it('responds with 400 (Bad Request) when the requested method is not a valid HTTP method', async () => {
-      const res = await next.fetch(basePath + '/status/405', {
-        method: 'HEADER',
-      })
+    if (!isNextDeploy) {
+      it('responds with 400 (Bad Request) when the requested method is not a valid HTTP method', async () => {
+        const res = await next.fetch(basePath + '/status/405', {
+          method: 'HEADER',
+        })
 
-      expect(res.status).toEqual(400)
-      expect(await res.text()).toBeEmpty()
-    })
+        expect(res.status).toEqual(400)
+        expect(await res.text()).toBeEmpty()
+      })
+    }
 
     it('responds with 405 (Method Not Allowed) when method is not implemented', async () => {
       const res = await next.fetch(basePath + '/status/405', {
@@ -690,7 +692,7 @@ describe('app-custom-routes', () => {
         await next.fetch(basePath + '/no-response', { method: 'POST' })
         await retry(() => {
           expect(next.cliOutput).toMatch(
-            /No response is returned from route handler '.+\/route\.ts'\. Ensure you return a `Response` or a `NextResponse` in all branches of your handler\./
+            /No response is returned from route handler '.+\/route\.ts'\. Expected a Response object but received '\w+' \(method: POST, url: .+\)\. Ensure you return a `Response` or a `NextResponse` in all branches of your handler\./
           )
         })
       })