cloudflare / cloudflare/vinext

Server Actions on dynamic PPR fallback routes: execute action but skip fallback rendering, extract RDC-only from postponed state

Open
#2,841 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:** [`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

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.