Skip to content

createRedirectRenderResult fetch missing redirect: "manual" — causes errors and wasted latency on server action redirects #90591

@ajworkos

Description

@ajworkos

Link to the code that reproduces this issue

https://github.com/ajworkos/nextjs-redirect-bug-repro

To Reproduce

  1. Create a server action that calls redirect('/api/login') (or any internal route)
  2. The route handler at /api/login itself calls redirect('https://external-oauth-provider.com/authorize?...') — i.e., it redirects externally
  3. Wrap the server action in a client component <form action={...}> so it goes through the RSC action handler path
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions