cloudflare / cloudflare/vinext
Server Actions: flush pending revalidations on error/notFound responses when page rendering is skipped
- Dominant language
- TypeScript
- Stars
- 8.8k
- Forks
- 406
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 120
Description
## Next.js Change
**Commit:** [`3904d0c`](https://github.com/vercel/next.js/commit/3904d0c291f7eb4c00c9ad469c653c43e9a4ce46)
**PR:** [#96945](https://github.com/vercel/next.js/pull/96945)
## What changed
When a Server Action **skips page rendering** (fetch action that didn't revalidate, or a forwarded/action-only request), the successful response path attached pending revalidations to the response's `waitUntil` promise. The **error paths did not** — including when the action calls `notFound()` or otherwise throws.
As a result, a forwarded action that called `revalidatePath()` or `revalidateTag()` and then threw could return its error response **without executing the pending invalidation**, silently dropping the revalidation.
### Mechanism (from the diff)
- Introduces a `getRevalidationWaitUntil(workStore, skipPageRendering)` helper in `action-handler.ts`:
- If `skipPageRendering` is `false`, returns `undefined` (page rendering executes pending revalidations before rendering, so no `waitUntil` is needed).
- If `skipPageRendering` is `true`, calls `executeRevalidates(workStore)` and returns its promise (or `undefined` when it returns `false`).
- The helper is now attached to **all** skipped-render `generateFlight(...)` calls — the successful fetch-action path, the `notFound()`/error path, and the general done path — not just the success path.
Touched file: `packages/next/src/server/app-render/action-handler.ts`.
## Impact on vinext
vinext reimplements Server Action handling and revalidation. If vinext only flushes pending revalidations (`revalidatePath`/`revalidateTag`) on the successful action response — or relies solely on page rendering to flush them — then an action that revalidates and then calls `notFound()`/throws will return its error response without the invalidation ever running. On Cloudflare Workers this is especially important since post-response work must be attached to `waitUntil`.
What to check/do:
1. **Centralize skipped-render revalidation handling.** When an action skips page rendering (forwarded, action-only, or no revalidation occurred), compute pending revalidations once.
2. **Attach the revalidation promise to both success and error responses** (including `notFound()` and thrown-error paths), via `ctx.waitUntil` / the platform's `waitUntil`.
3. **Preserve existing behavior for actions that continue into page rendering** — those flush revalidations before rendering and should not double-attach a `waitUntil`.
4. **Port the regression test.** Next.js added a production test: an action calls `revalidatePath()` then `notFound()`, asserts the fallback page is not rendered, and asserts the cache handler receives the expected path-tag invalidation.
## Related
- #1225 — Server action forwarding loop with middleware rewrites
Contributor guide
Research direction
Compare vinext's Server Action handling and waitUntil integration with packages/next/src/server/app-render/action-handler.ts, focusing on skipped-render success, notFound(), and thrown-error paths. Port the described production regression test: revalidatePath() followed by notFound() must skip fallback rendering while still delivering the cache invalidation through waitUntil without double-attaching during page rendering.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, typescript
- Domain
- api, backend, backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100