next.js: Next Redirect - Server action redirects do not execute when inside try / catch blocks

Link to the code that reproduces this issue

https://github.com/Pbmangan/new-app/blob/main/app/action/page.tsx

To Reproduce

  1. Create simple server action with redirect to relative or absolute route (ensure it executes)
  2. Add a try / catch block around the redirect code
  3. See failure message such as below 4.NEXT_REDIRECT at getRedirectError (webpack-internal:///(rsc)/./node_modules/next/dist/client/components/redirect.js:40:19) at redirect (webpack-internal:///(rsc)/./node_modules/next/dist/client/components/redirect.js:50:11) at $$ACTION_0 (webpack-internal:///(rsc)/./components/server-actions/action.ts:97:70)

Current vs. Expected behavior

Currently redirect intuitively would be used inside try catch blocks - such as when creating a customer checkout session for stripe. The Docs do not highlight the issue and it was not intuitive to solve.

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.6.0: Wed Jul  5 22:22:05 PDT 2023; root:xnu-8796.141.3~6/RELEASE_ARM64_T6000
    Binaries:
      Node: 20.5.0
      npm: 2.15.12
      Yarn: 1.22.19
      pnpm: N/A
    Relevant Packages:
      next: 13.4.19
      eslint-config-next: 13.4.19
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 4.9.4
    Next.js Config:
      output: N/A

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

App Router

Additional context

No response

About this issue

  • Original URL
  • State: closed
  • Created 9 months ago
  • Reactions: 15
  • Comments: 20 (6 by maintainers)

Most upvoted comments

This feels like really unexpected behavior. A server action might frequently take this form.

async function createAndNavigateToPost(post) {
  try {
     const newPost = await insertPostIntoDb(post)
     redirect(`/posts/${newPost.id}`)
  } catch (e) {
    // make the error visible to the form
    return {
      message: e.message
    }
  }
} 

Having to always check if the error is a redirect error is really unexpected behavior and pollutes the application code with framework idiosyncrasies.

@Pbmangan I had the same issue as yourself. From looking at the code and what I can workout the redirect function, essentially throws a special type of error, and then when the page function throws and error, next checks to see if is of the redirect type and processes it. Luckily there is a function isRedirectError you can use this in your catch block.

function redirect(url, type) {
    if (type === void 0) type = "replace";
    throw getRedirectError(url, type);
}
import { isRedirectError } from "next/dist/client/components/redirect";
import { redirect } from "next/navigation";

export default async function Page() {
  try {
//API Calls to get data 
  redirect(`/new-route`);
 } catch (err: any) {
   if (isRedirectError(err)) {
      throw err;
    } else {
      console.log(err)
      console.log("ERROR FROM OTHER OPERATIONS")
      throw err
    }
  }
}

Hope this helps

This is intended – redirect() throws NEXT_REDIRECT and returns the TypeScript never type. It should be called outside of your try/catch block, or handled explicitly. We’ve updated the docs to change the code blocks for Server Actions to place the redirect() after the try/catch block. https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#redirecting

Hmm… it’s quite confusing. According to the NextJS authentication learning doc, the next-auth signIn function is being called in try/catch block, and the function contains redirect from next/navigation.

Aha, I think I understand now. So technically, we can still call redirect within a try/catch block. However, we need to ensure that we throw the original error. Here’s an example:

try {
  redirect('/~');
} catch (error) {
  throw error; // <-- We need to throw this error to ensure that the redirect works.
}

Instead of mentioning that redirect cannot be used in a try/catch block in the documentation, it would be helpful to explain why and provide the workaround.

(Edit: Didn’t realize someone already mentioned the exact same thing above, my bad)

This is intended – redirect() throws NEXT_REDIRECT and returns the TypeScript never type. It should be called outside of your try/catch block, or handled explicitly. We’ve updated the docs to change the code blocks for Server Actions to place the redirect() after the try/catch block.

https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#redirecting

Shouldn’t the redirect still be executed in this case regardless of the catch block?