[PR #910] [MERGED] fix(app-router): action redirects #940

Closed
opened 2026-05-06 13:10:57 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/910
Author: @NathanDrake2406
Created: 4/26/2026
Status: Merged
Merged: 4/27/2026
Merged by: @james-elicx

Base: mainHead: nathan/app-post-form-actions


📝 Commits (5)

  • 3258947 Fix app form action redirects
  • 22e4f17 Trigger CI rerun
  • 3f4c5d0 Refactor progressive action handling
  • ff42478 Address app action review feedback
  • 7a263b4 Trigger CI rerun

📊 Changes

15 files changed (+960 additions, -28 deletions)

View changed files

📝 packages/vinext/src/entries/app-rsc-entry.ts (+32 -3)
📝 packages/vinext/src/server/app-route-handler-execution.ts (+17 -6)
📝 packages/vinext/src/server/app-route-handler-policy.ts (+22 -1)
packages/vinext/src/server/app-server-action-execution.ts (+210 -0)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+168 -18)
📝 tests/app-route-handler-policy.test.ts (+72 -0)
📝 tests/app-router.test.ts (+7 -0)
tests/app-server-action-execution.test.ts (+296 -0)
📝 tests/e2e/app-router/server-actions.spec.ts (+29 -0)
tests/fixtures/app-basic/app/nextjs-compat/action-progressive/page.tsx (+33 -0)
tests/fixtures/app-basic/app/nextjs-compat/action-progressive/result/page.tsx (+18 -0)
tests/fixtures/app-basic/app/nextjs-compat/api/post-form-permanent-redirect/route.ts (+5 -0)
tests/fixtures/app-basic/app/nextjs-compat/api/post-form-redirect/route.ts (+9 -0)
tests/fixtures/app-basic/app/nextjs-compat/route-handler-redirects/page.tsx (+7 -0)
📝 tests/nextjs-compat/app-routes.test.ts (+35 -0)

📄 Description

What changed

  • Treat no-JS multipart App Router server action form submissions as possible MPA server actions, decode them with React's decodeAction, and return a real HTTP 303 redirect when the action calls redirect().
  • Classify app route-handler POST form submissions as action-like for redirect handling, so redirect() and permanentRedirect() return 303 instead of replay-prone 307/308 semantics.
  • Preserve pending cookies() / draft-mode cookies when a route handler throws a redirect, matching Next.js app-route behavior.
  • Add Next.js-compat fixtures and tests for no-JS server action redirects and POST form route-handler redirects.

Why

Browser form submissions without JavaScript send multipart/form-data and no RSC action header. The previous implementation only handled POSTs with x-rsc-action, so progressive-enhancement server action forms fell through to a normal render and the action was silently skipped.

For route handlers, redirect() encodes 307/308 in the digest, but Next.js overrides action-like POST redirects to 303 so the browser follows with GET instead of resubmitting the original POST.

Next.js references

  • getServerActionRequestMetadata(): marks POST multipart/form-data, POST application/x-www-form-urlencoded, and POST action-header requests as possible server actions.
  • action-handler.ts: multipart non-fetch actions call decodeAction(formData, serverModuleMap) and return through the full-page render path for progressive enhancement.
  • app-route/module.ts: app route redirects return RedirectStatusCode.SeeOther when actionStore.isAction is true, and append mutable cookies to the redirect response.
  • app-action-progressive-enhancement.test.ts: verifies formData + redirect without JavaScript returns 303 and navigates.
  • app-action.test.ts: verifies POST route-handler redirect() / permanentRedirect() flows use 303.

Validation

  • vp check
  • vp test run tests/app-route-handler-policy.test.ts
  • vp test run tests/nextjs-compat/app-routes.test.ts
  • vp test run tests/app-router.test.ts -t "server action"
  • vp test run tests/entry-templates.test.ts
  • vp run vinext#build
  • PLAYWRIGHT_PROJECT=app-router vp exec playwright test tests/e2e/app-router/server-actions.spec.ts

Note: the local pre-commit hook failed in this worktree during Vitest suite initialization before running assertions (Cannot read properties of undefined (reading 'config')). The same snapshot update/test command passes when run directly through vp, and the updated snapshot is committed.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/910 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 4/26/2026 **Status:** ✅ Merged **Merged:** 4/27/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `nathan/app-post-form-actions` --- ### 📝 Commits (5) - [`3258947`](https://github.com/cloudflare/vinext/commit/325894733185f57ca3b5c82701e0b09c1af534db) Fix app form action redirects - [`22e4f17`](https://github.com/cloudflare/vinext/commit/22e4f172551497f462f5765bed3a3ffb14f0c87d) Trigger CI rerun - [`3f4c5d0`](https://github.com/cloudflare/vinext/commit/3f4c5d0169924760e109d59932c3a29413d28d28) Refactor progressive action handling - [`ff42478`](https://github.com/cloudflare/vinext/commit/ff4247878c1aa3ca55717df238ab0e39d56210cf) Address app action review feedback - [`7a263b4`](https://github.com/cloudflare/vinext/commit/7a263b49067e3f4255318c29f49eceb2036f0223) Trigger CI rerun ### 📊 Changes **15 files changed** (+960 additions, -28 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/entries/app-rsc-entry.ts` (+32 -3) 📝 `packages/vinext/src/server/app-route-handler-execution.ts` (+17 -6) 📝 `packages/vinext/src/server/app-route-handler-policy.ts` (+22 -1) ➕ `packages/vinext/src/server/app-server-action-execution.ts` (+210 -0) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+168 -18) 📝 `tests/app-route-handler-policy.test.ts` (+72 -0) 📝 `tests/app-router.test.ts` (+7 -0) ➕ `tests/app-server-action-execution.test.ts` (+296 -0) 📝 `tests/e2e/app-router/server-actions.spec.ts` (+29 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/action-progressive/page.tsx` (+33 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/action-progressive/result/page.tsx` (+18 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/api/post-form-permanent-redirect/route.ts` (+5 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/api/post-form-redirect/route.ts` (+9 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/route-handler-redirects/page.tsx` (+7 -0) 📝 `tests/nextjs-compat/app-routes.test.ts` (+35 -0) </details> ### 📄 Description ## What changed - Treat no-JS multipart App Router server action form submissions as possible MPA server actions, decode them with React's `decodeAction`, and return a real HTTP 303 redirect when the action calls `redirect()`. - Classify app route-handler POST form submissions as action-like for redirect handling, so `redirect()` and `permanentRedirect()` return 303 instead of replay-prone 307/308 semantics. - Preserve pending `cookies()` / draft-mode cookies when a route handler throws a redirect, matching Next.js app-route behavior. - Add Next.js-compat fixtures and tests for no-JS server action redirects and POST form route-handler redirects. ## Why Browser form submissions without JavaScript send `multipart/form-data` and no RSC action header. The previous implementation only handled POSTs with `x-rsc-action`, so progressive-enhancement server action forms fell through to a normal render and the action was silently skipped. For route handlers, `redirect()` encodes 307/308 in the digest, but Next.js overrides action-like POST redirects to 303 so the browser follows with GET instead of resubmitting the original POST. ## Next.js references - [`getServerActionRequestMetadata()`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/server-action-request-meta.ts): marks POST `multipart/form-data`, POST `application/x-www-form-urlencoded`, and POST action-header requests as possible server actions. - [`action-handler.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/action-handler.ts): multipart non-fetch actions call `decodeAction(formData, serverModuleMap)` and return through the full-page render path for progressive enhancement. - [`app-route/module.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/route-modules/app-route/module.ts): app route redirects return `RedirectStatusCode.SeeOther` when `actionStore.isAction` is true, and append mutable cookies to the redirect response. - [`app-action-progressive-enhancement.test.ts`](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/actions/app-action-progressive-enhancement.test.ts): verifies formData + redirect without JavaScript returns 303 and navigates. - [`app-action.test.ts`](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/actions/app-action.test.ts): verifies POST route-handler `redirect()` / `permanentRedirect()` flows use 303. ## Validation - `vp check` - `vp test run tests/app-route-handler-policy.test.ts` - `vp test run tests/nextjs-compat/app-routes.test.ts` - `vp test run tests/app-router.test.ts -t "server action"` - `vp test run tests/entry-templates.test.ts` - `vp run vinext#build` - `PLAYWRIGHT_PROJECT=app-router vp exec playwright test tests/e2e/app-router/server-actions.spec.ts` Note: the local pre-commit hook failed in this worktree during Vitest suite initialization before running assertions (`Cannot read properties of undefined (reading 'config')`). The same snapshot update/test command passes when run directly through `vp`, and the updated snapshot is committed. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:10:57 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#940
No description provided.