-
Notifications
You must be signed in to change notification settings - Fork 30.6k
Description
Link to the code that reproduces this issue
https://github.com/ajworkos/nextjs-redirect-bug-repro
To Reproduce
- Create a server action that calls
redirect('/api/login')(or any internal route) - The route handler at
/api/loginitself callsredirect('https://external-oauth-provider.com/authorize?...')— i.e., it redirects externally - Wrap the server action in a client component
<form action={...}>so it goes through the RSC action handler path - Submit the form
Expected: The client receives the redirect and navigates to the external URL.
Actual (Demo 1 — error): If the external URL has a redirect chain (common with OAuth/SSO providers), the server-side fetch in createRedirectRenderResult follows all redirects and hits the "redirect count exceeded" limit, causing a 500 error.
Actual (Demo 2 — latency): Even when the chain is short enough to not error, the server unnecessarily fetches the external redirect target (e.g., a slow OAuth authorization page), adding seconds of wasted latency before the client gets the redirect response.
Current vs Expected behavior
Current: createRedirectRenderResult in packages/next/src/server/app-render/action-handler.ts makes a fetch() call without redirect: 'manual', causing it to automatically follow the entire redirect chain of the target URL server-side.
Expected: The fetch should use redirect: 'manual' (just like the sibling function createForwardedActionResponse already does), so the redirect is handed back to the client via the x-action-redirect header without following it server-side.
Root Cause
PR #65097 correctly added redirect: 'manual' to the createForwardedActionResponse function but missed applying the same fix to createRedirectRenderResult. The two functions serve similar roles (forwarding action responses), and the inconsistency means createRedirectRenderResult still follows redirects automatically.
The relevant code is at:
// createForwardedActionResponse — has the fix ✅
const response = await fetch(fetchUrl, {
method: 'POST',
body,
duplex: 'half',
headers: forwardedHeaders,
redirect: 'manual', // ← present
next: { internal: 1 },
})
// createRedirectRenderResult — missing the fix ❌
const response = await fetch(fetchUrl, {
method: 'GET',
headers: forwardedHeaders,
// ← redirect: 'manual' missing
next: { internal: 1 },
})
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 23.6.0
Binaries:
Node: 20.17.0
npm: 10.8.2
pnpm: 9.15.0
Runtime Versions:
next: 16.1.6
Which area(s) are affected? (Select all that apply)
Server Actions
Which stage(s) are affected? (Select all that apply)
next dev, next build (production), Deployed (Vercel or other)
Additional context
This is a one-line fix: add redirect: 'manual' to the fetch call in createRedirectRenderResult, matching what #65097 already did for createForwardedActionResponse.
I have a PR ready with the fix: #90137
The real-world impact is significant for any app where server actions redirect to route handlers that perform OAuth/SSO redirects (a very common pattern). We discovered this while upgrading a production auth application from Next.js 14 to 16. In v14, we had been carrying a workaround using relative URLs to avoid this codepath, but the underlying bug has existed since createRedirectRenderResult was introduced.