Skip to content

Using notFound causes ISR to use the wrong status code #78432

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
cshawaus opened this issue Apr 23, 2025 · 0 comments
Open

Using notFound causes ISR to use the wrong status code #78432

cshawaus opened this issue Apr 23, 2025 · 0 comments
Labels
Not Found Related to the not-found.tsx file or the notFound() function. Runtime Related to Node.js or Edge Runtime with Next.js.

Comments

@cshawaus
Copy link

cshawaus commented Apr 23, 2025

Link to the code that reproduces this issue

https://github.com/cshawaus/notfound-isr-status-code

To Reproduce

  1. Build the application for production (next build)
  2. Start the application (next start)
  3. Wait until your clock seconds are greater than 30
  4. Open the application in your browser
  5. Wait 30 seconds and refresh the page
  6. Wait another 60 seconds, and refresh the page again
  7. Wait another 30 seconds, and refresh the page again

Current vs. Expected behavior

Current

Currently, ISR produces an incorrect 404 status code when restoring from the cache if the previous response was a 404. This error occurs when the page content is resolved normally without the existence of __next_error__ in the HTML anymore, due to a previous error.

The 404 status code remains until either revalidatePath or revalidateTag is called.

Expected

The expected behaviour is that the previous cache should be ignored during revalidation, and a fresh 200 status code is generated instead of reusing the old one.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.4.0: Wed Mar 19 21:17:25 PDT 2025; root:xnu-11417.101.15~1/RELEASE_ARM64_T6020
  Available memory (MB): 32768
  Available CPU cores: 12
Binaries:
  Node: 22.13.1
  npm: 10.9.2
  Yarn: 1.22.19
  pnpm: 9.15.9
Relevant Packages:
  next: 15.3.1 // Latest available version is detected (15.3.1).
  eslint-config-next: 15.3.1
  react: 19.1.0
  react-dom: 19.1.0
  typescript: 5.8.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Not Found, Runtime

Which stage(s) are affected? (Select all that apply)

next start (local)

Additional context

I haven't been able to track down the root cause yet, but I was able to patch packages/next/src/server/base-server.ts locally.

// If the request is a data request, then we shouldn't set the status code
// from the response because it should always be 200. This should be gated
// behind the experimental PPR flag.
if (cachedData.status && (!isRSCRequest || !isRoutePPREnabled)) {
res.statusCode = cachedData.status
}

There is an assumption that a 200 status code should be present, but because the previous response cache is used to fill in some fields, 404 is also possible. See my patch below.

res.statusCode =
  !cachedData.html.isDynamic &&
  cachedData.html.toUnchunkedString().includes('__next_error__')
    ? 404
    : 200
diff --git a/dist/esm/server/base-server.js b/dist/esm/server/base-server.js
index 37f85b41274b2e32ab9551a4e1e7d2e4fcbe0045..1ef62fda97224fbcb60c94286865e094d95ebb28 100644
--- a/dist/esm/server/base-server.js
+++ b/dist/esm/server/base-server.js
@@ -2144,7 +2144,11 @@ export default class Server {
             // from the response because it should always be 200. This should be gated
             // behind the experimental PPR flag.
             if (cachedData.status && (!isRSCRequest || !isRoutePPREnabled)) {
-                res.statusCode = cachedData.status;
+                res.statusCode =
+                    !cachedData.html.isDynamic &&
+                    cachedData.html.toUnchunkedString().includes('__next_error__')
+                        ? 404
+                        : 200;
             }
             // Mark that the request did postpone.
             if (didPostpone) {
diff --git a/dist/server/base-server.js b/dist/server/base-server.js
index ae2c3bb28e5ab33b57322210e4b94be2f5907267..52758fd0b7bcada9b3ccb361f9cdb98ca621e92b 100644
--- a/dist/server/base-server.js
+++ b/dist/server/base-server.js
@@ -2213,7 +2213,11 @@ class Server {
             // from the response because it should always be 200. This should be gated
             // behind the experimental PPR flag.
             if (cachedData.status && (!isRSCRequest || !isRoutePPREnabled)) {
-                res.statusCode = cachedData.status;
+                res.statusCode =
+                    !cachedData.html.isDynamic &&
+                    cachedData.html.toUnchunkedString().includes('__next_error__')
+                        ? 404
+                        : 200;
             }
             // Mark that the request did postpone.
             if (didPostpone) {

I don't believe this patch causes any side effects. However, because the assumption is that only a 200 status code should be present, there is no way to predict what other possible status codes might be present. In theory, only 200 and 404 should be possible, but given app-render runtime seems to respond incorrectly, a more robust check needs to exist here to validate the outcome before the handler responds.

EDIT
Upon further digging, I suspect the following app-render status code checks are related, given the lack of additional places where res.statusCode is set.

res.statusCode = getAccessFallbackHTTPStatus(err)

res.statusCode = getAccessFallbackHTTPStatus(err)

I suspect app-render doesn't have the proper context to determine when the status code needs to remain a 200, and simply sets it to 404 because of the previous error.

@github-actions github-actions bot added Not Found Related to the not-found.tsx file or the notFound() function. Runtime Related to Node.js or Edge Runtime with Next.js. labels Apr 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not Found Related to the not-found.tsx file or the notFound() function. Runtime Related to Node.js or Edge Runtime with Next.js.
Projects
None yet
Development

No branches or pull requests

1 participant