vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.32k stars 27.02k forks source link

revalidatePath or revalidateTag does not execute internal revalidation logic if called inside a setTimeout nor does it throw an exception #72578

Open tilman opened 2 weeks ago

tilman commented 2 weeks ago

Link to the code that reproduces this issue

https://github.com/trieb-work/nextjs-bug-revalidatetag-inside-settimeout

To Reproduce

  1. start the app with nodejs in production mode: NODE_ENV=production VERCEL_ENV=production pnpm build && NODE_ENV=production VERCEL_ENV=production pnpm start
  2. open the index page http://localhost:3000 --> a random number will appear which will get cached
  3. reload the index page: the same number is still present because it is cached
  4. open http://localhost:3000/normal --> revalidatePath will get called for the index page
  5. open the index page --> a new number will be displayed because of the working revalidatePath
  6. open http://localhost:3000/timeout --> revalidatePath will get called inside a setTimeout
  7. open the index page --> the same old number is still present because revalidateTag/revalidatePath does not work inside of a setTimeout

Current vs. Expected behavior

if revalidateTag/revalidatePath is used inside a setTimeout it does not have any effect (tested in nodejs), but still the remaining code inside the setTimeout is executed. I implemented my own cache handler (like here: https://nextjs.org/docs/app/api-reference/next-config-js/incrementalCacheHandlerPath) and could see that the revalidateTag function from the cache handler is never called if the nextjs revalidateTag function is called inside a setTimeout.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 22.6.0: Wed Jul  5 22:22:05 PDT 2023; root:xnu-8796.141.3~6/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 20.18.0
  npm: 10.8.2
  Yarn: 1.22.21
  pnpm: 8.15.8
Relevant Packages:
  next: 15.0.4-canary.5 // Latest available version is detected (15.0.4-canary.5).
  eslint-config-next: N/A
  react: 19.0.0-rc-5c56b873-20241107
  react-dom: 19.0.0-rc-5c56b873-20241107
  typescript: 5.3.3
Next.js Config:
  output: N/A

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

Performance, Runtime

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

Other (Deployed)

Additional context

I have not tested it in deployed vercel env as we are self hosting using docker and nodejs.

I have also tested to wrap the setTimeout in an unstable_after function but this has not helped as well.

As a workaround the code can be rewritten to use a sleep/delay function (const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));) instead of a setTimout directly. Calling revalidateTag after the sleep function works as expected (await sleep(100); revalidatePath("/"))

tilman commented 2 weeks ago

UPDATE: I have added a new example to the bug repo: awaitedTimeout I this example the setTimeout is working. Most likely due to being awaited and the response is sent after the revalidatePath was called and not the other way around as in the regular timeout example route