nextjs - 💡(How to fix) Fix PPR fallback serves prerendered shell for server action POSTs in custom server setup (cacheComponents: true) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#91748Fetched 2026-04-08 01:07:05
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Timeline (top)
labeled ×2closed ×1commented ×1locked ×1

Root Cause

Root cause: In packages/next/src/build/templates/app-page.ts, the PPR fallback condition does not check isPossibleServerAction:

Fix Action

Fix / Workaround

Workaround: Using patch-package to apply the one-line fix to node_modules/next/dist/build/templates/app-page.js (and the ESM variant at node_modules/next/dist/esm/build/templates/app-page.js), then rebuilding.

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
Node.js: v24.14.0
Next.js: 16.2.0
Custom server: NestJS/Express (not next start)
cacheComponents: true (PPR enabled)

---

// Current (buggy):
if (isRoutePPREnabled && (nextConfig.cacheComponents ? !isDynamicRSCRequest : !isRSCRequest)) {

---

- if (isRoutePPREnabled && (nextConfig.cacheComponents ? !isDynamicRSCRequest : !isRSCRequest)) {
+ if (isRoutePPREnabled && !isPossibleServerAction && (nextConfig.cacheComponents ? !isDynamicRSCRequest : !isRSCRequest)) {
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

N/A (custom server setup — reproduction steps below)

To Reproduce

  1. Create a Next.js app with cacheComponents: true and a custom Express/NestJS server (not using next start)
  2. Add a dynamic route (e.g., [orgSlug]/app/reports/page.tsx) with a server action that calls redirect()
  3. Build the app (next build)
  4. Serve with the custom server
  5. Submit the form — the server action POST receives the prerendered PPR shell instead of executing the action

Current vs. Expected behavior

Current: Server action POSTs to dynamic routes with PPR/cacheComponents return the prerendered static shell (200 + full page RSC tree). The action handler (handleAction) is never invoked. The experimentalBypassFor: [{ type: "header", key: "next-action" }] in the prerender manifest is only read by the router-server, which custom servers bypass.

Expected: Server action POSTs should execute the action and return the action result (redirect response with x-action-redirect, state update, etc.), not the prerendered shell.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
Node.js: v24.14.0
Next.js: 16.2.0
Custom server: NestJS/Express (not next start)
cacheComponents: true (PPR enabled)

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

App Router, PPR, Server Actions

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

next build (and runtime with custom server)

Additional context

Root cause: In packages/next/src/build/templates/app-page.ts, the PPR fallback condition does not check isPossibleServerAction:

// Current (buggy):
if (isRoutePPREnabled && (nextConfig.cacheComponents ? !isDynamicRSCRequest : !isRSCRequest)) {

Server action POSTs don't carry the RSC: 1 header, so isDynamicRSCRequest is false. The condition evaluates to true and the fallback shell is served via routeModule.handleResponse({ isFallback: true, forceStaticRender: true }), completely bypassing the action handler.

The isPossibleServerAction variable is already computed earlier in the same handler (via getIsPossibleServerAction(req)) and is already used to exclude server actions from the ssgCacheKey assignment a few lines above. The PPR fallback condition simply missed the same check.

Suggested one-line fix:

- if (isRoutePPREnabled && (nextConfig.cacheComponents ? !isDynamicRSCRequest : !isRSCRequest)) {
+ if (isRoutePPREnabled && !isPossibleServerAction && (nextConfig.cacheComponents ? !isDynamicRSCRequest : !isRSCRequest)) {

Why this only affects custom servers: In the normal next start flow, the router-server reads experimentalBypassFor from the prerender manifest (which correctly lists next-action as a bypass header) and skips the prerender cache before the request reaches the page handler. Custom servers bypass the router-server entirely, so this protection is never applied — making the missing check in the page handler template the only line of defense.

Workaround: Using patch-package to apply the one-line fix to node_modules/next/dist/build/templates/app-page.js (and the ESM variant at node_modules/next/dist/esm/build/templates/app-page.js), then rebuilding.

extent analysis

Fix Plan

To fix the issue, you need to update the PPR fallback condition in app-page.ts to check isPossibleServerAction.

Here are the steps:

  • Update the condition to include the isPossibleServerAction check:
if (isRoutePPREnabled && !isPossibleServerAction && (nextConfig.cacheComponents ? !isDynamicRSCRequest : !isRSCRequest)) {
  • If you cannot update the next package directly, use patch-package to apply the fix to node_modules/next/dist/build/templates/app-page.js and node_modules/next/dist/esm/build/templates/app-page.js.
  • Rebuild your Next.js app after applying the fix.

Verification

To verify that the fix worked:

  • Run your Next.js app with the custom server.
  • Submit the form that triggers the server action POST.
  • Check that the server action handler is invoked and the expected response is returned (e.g., a redirect response with x-action-redirect).

Extra Tips

  • Make sure to update your next package to the latest version when available, as this fix may be included in future releases.
  • If you're using a custom server, ensure that you're handling server actions correctly and not bypassing the router-server unnecessarily.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING