-
Notifications
You must be signed in to change notification settings - Fork 29.7k
RFC: unstable_rethrow API
#64076
-
Co-authored by: @wyattjoh
Goals
- Make it easier for developers to write error-safe code that utilizes
try/catchblocks around Next.js APIs that rely on throwing - Avoid developers having to reach for internals to solve this issue, or compare against magic strings
Background
The following Next.js APIs rely on throwing an error which should be rethrown and handled by Next.js itself.:
Next.js throws errors to communicate navigation intent to interrupt the code execution and React component rendering. These errors propagate up to the browser when streaming to allow the browser to perform the action, change the status code and/or return a different page when running on the server.
If a route segment is marked to throw an error unless it's static, a dynamic function call will also throw an error that should similarly not be caught by the developer. Note that Partial Prerendering (PPR) affects this behavior as well. These APIs are:
Existing issues
Developers are currently asked to call these APIs outside try/catch blocks, but this is easy to miss or not always ergonomic:
- redirect inside try/catch block in server action throws NEXT_REDIRECT error #59930
- Redirect inside server action throws an error #58002
- Next Redirect - Server action redirects do not execute when inside try / catch blocks #55586
- NEXT_REDIRECT in ServerActions #50757
- Error: NEXT_REDIRECT while using server actions #49298
- Using signIn() in a custom signin page with redirect: false throws error on redirect nextauthjs/next-auth#8027
- Getting NEXT_REDIRECT instance of Error when using Next 14 to sign in nextauthjs/next-auth#10056
Proposal
Next.js will provide a new API, that will work both on the client and server: function unstable_rethrow(error: unknown): void | never, which handles all Next.js errors by re-throwing them to allow Next.js to take over the error handling, and continue execution for all other cases so the developer can provide their own error-handling mechanism.
This method should be called at the top of the catch block, passing the error object as its only argument. It can also be used within a .catch handler of a promise.
Additionally, if you defined your own [ErrorBoundary component](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary), unstable_rethrow should also be called at the top of the getDerivedStateFromError and componentDidCatch methods. Note, if you used the Next.js error.js file, Next.js will re-throw those errors for you.
It's important to note though that these navigation APIs (like notFound() or redirect()) will always throw when called, this tool is useful but you may not need it. If you ensure that your calls to API's that throw are not wrapped in a try/catch then you shouldn't required unstable_rethrow.
It's important to know that unstable_rethrow, if passed an internal Next.js errror, it will throw. This means that any resource cleanup (like clearing intervals, timers, etc) would have to either happen prior to the call to unstable_rethrow or within a finally block.
Example usage:
Example using a try/catch block:
import { unstable_rethrow as rethrow } from 'next' import { createOrder } from '@/lib' import { redirect } from 'next/navigation' export default function Page() { return ( <form action={async (formData: FormData) => { 'use server' try { const res = await createOrder(formData) redirect(res.url) } catch (error) { rethrow(error) // This handles Next.js errors // Handle non-Next.js errors } }} > {/* ... */} </form> ) }
Or using a promise's .catch handler:
import { unstable_rethrow as rethrow } from 'next' import { login } from '@/lib' import { redirect } from 'next/navigation' export default function Page() { return ( <form action={async (formData: FormData) => { 'use server' await login(formData).catch((error) => { rethrow(error) // This handles Next.js errors // Handle non-Next.js errors }) }} > {/* ... */} </form> ) }
Caveats
The unstable_rethrow API will not inspect error objects that have their source errors nested. If a library calls redirect() within a try/catch block, but then nests the error like:
async function api() { try { // ... redirect(...) } catch (cause) { // should have called `unstable_rethrow(cause)` here! throw new Error("Something bad happened!", { cause }) } } async function Component() { try { await api() } catch (err) { // won't work unstable_rethrow(err) // will work, better to avoid nesting or calling `unstable_rethrow` whenever // a `try/catch` block is used. unstable_rethrow(err.cause) } // ... }
Alternatives
- Do nothing - expose API's for users to inspect the errors themselves. This results in more custom code.
- Don't use throwing in Next.js API's - without throwing, we have no way to inurrupt the Next.js/React render pipeline, difficult to mark and track usage of dynamic data.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 32 -
😕 2 -
🚀 8
Replies: 3 comments 1 reply
-
I agree in the importance of this API in addition to checking against both error and error.cause by default.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
[...] without throwing, we have no way to inurrupt the Next.js/React render pipeline, difficult to mark and track usage of dynamic data.
I find this to be a reasonable solution if redesigning the public APIs to not throw errors is infeasible. I think it is intuitive that dynamic functions (cookies() et al) throw when a segment is set to dynamic = 'error', as you're explicitly opting into this behavior. However, I think what makes this pattern unintuitive cannot be fixed with an escape hatch such as this proposal.
async function action() { try { ... // This just feels like a dangling promise and a missing `return` before `redirect()` redirect(...) } catch(err) { ... } }
The proposed unstable_rethrow(err) is not dissimilar to the redirect family in its uncomfortable ability to opaquely disrupt control flow unless you know what it is rethrowing. Accidentally bailing before cleanup as mentioned seems like another easy footgun and documentation that needs to be written when prior APIs let you just return <redirect method>. If this loss of readability and explicitness is a byproduct of the render pipeline as it is today, this is understandable, but otherwise throwing things that aren't actual errors is going to confuse developers and I'd rather try to avoid try/catch around these methods altogether.
Beta Was this translation helpful? Give feedback.
All reactions
-
in the server side gives me error while call unstable_rethrow ```
(0 , next_navigation__WEBPACK_IMPORTED_MODULE_5__.unstable_rethrow) is not a function
Beta Was this translation helpful? Give feedback.
All reactions
-
What is the next version in your package.json?
Beta Was this translation helpful? Give feedback.