cloudflare / cloudflare/vinext
Server Actions on dynamic PPR fallback routes: execute action but skip fallback rendering, extract RDC-only from postponed state
- Dominant language
- TypeScript
- Stars
- 8.8k
- Forks
- 406
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 120
Description
## Next.js Change
**Commit:** [`1ab0f1a`](https://github.com/vercel/next.js/commit/1ab0f1ada438a699fc9a64818a2726268af5bfa2)
**PR:** [#96932](https://github.com/vercel/next.js/pull/96932)
## What changed
A fetch action can be dispatched to a **parameterized route's fallback** without concrete route params. When the deployment adapter also supplies that route's **postponed (PPR) state**, Next.js previously threw because postponed state and fallback params were present together.
Such requests only need to **execute the action** — they cannot safely render the fallback route (there are no concrete params to interpolate). The fix detects this "action-only fallback request", preserves the Resume Data Cache for cached reads, discards the React postponed state that can't be resumed without concrete params, and consistently **skips fallback-route rendering** for both successful and failed actions.
### Mechanism (from the diff)
- `action-handler.ts` introduces:
```ts
const isActionOnlyFallbackRequest =
isFetchAction &&
requestStore.fallbackParams != null &&
typeof ctx.renderOpts.postponed === 'string'
const shouldSkipPageRendering = actionWasForwarded || isActionOnlyFallbackRequest
```
`shouldSkipPageRendering` now replaces the old `actionWasForwarded` in every place that decided whether to skip rendering (the `executeActionAndPrepareForRender` param, the `generateFlight({ skipPageRendering })` calls, and the `pathWasRevalidated`-based skip check).
- `postponed-state.ts` is refactored to extract `parsePostponedStateParts()` and adds `parseResumeDataCacheFromPostponedState()`, which extracts **only the Resume Data Cache** from a postponed-state string (falling back to an empty RDC on parse failure) — used so cached reads still work while the un-resumable postponed React state is discarded.
- `app-render.tsx` wires the RDC-only extraction into the action-only fallback path.
Touched files: `action-handler.ts`, `app-render.tsx`, `postponed-state.ts` (+ `postponed-state.test.ts`).
## Impact on vinext
vinext reimplements Server Actions, PPR/postponed-state resume, and the Resume Data Cache. If a fetch action targets a dynamic route's fallback (no concrete params) while postponed state is present, vinext must not try to render/resume the fallback page — doing so either throws (params vs postponed conflict) or produces an incorrect render. This matters for deployment adapters (including Cloudflare) that carry postponed state on fallback routes.
What to check/do:
1. **Detect action-only fallback requests:** fetch action + fallback params present + postponed state present ⇒ execute the action but **skip fallback-route rendering** for both success and failure.
2. **Preserve the Resume Data Cache** from the postponed state so cached reads inside the action still work, but **discard the React postponed state** that can't be resumed without concrete params. Add a helper equivalent to `parseResumeDataCacheFromPostponedState()` that extracts only the RDC (with an empty-RDC fallback on parse error).
3. **Unify the skip-render decision** so forwarded actions and action-only fallback requests share one `shouldSkipPageRendering` flag across all action response paths.
4. **Port the regression tests:** the `next start` action-only fallback synthetic request, access-fallback error coverage, and direct RDC extraction, plus `postponed-state` unit tests.
## Related
- #1935 — App Router PPR resume: treat empty postponed bodies as dynamic render (cold RDC resume)
- #2840 — Server Actions: flush pending revalidations on error/notFound responses when page rendering is skipped
- #1225 — Server action forwarding loop with middleware rewrites
Contributor guide
Research direction
Start by locating vinext's Server Actions, PPR/postponed-state, and Resume Data Cache entry points corresponding to action-handler.ts, app-render.tsx, and postponed-state.ts. Run or add the action-only fallback, access-fallback error, direct RDC extraction, and postponed-state tests described in the issue. Done means actions execute without fallback rendering, RDC reads remain available, React postponed state is discarded, and success and failure paths share the skip-render behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, typescript
- Domain
- api, backend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100