cloudflare / cloudflare/vinext

Server Actions: flush pending revalidations on error/notFound responses when page rendering is skipped

Open
#2,840 0 comments 0 reactions 0 assignees View on GitHub
nextjs-tracking
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.