Skip to content

Commit bc4078e

Browse files
authored
Bubble middleware errors back up the ancestor chain (#14118)
1 parent 9147bf7 commit bc4078e

File tree

7 files changed

+756
-500
lines changed

7 files changed

+756
-500
lines changed

.changeset/olive-bugs-sing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
[UNSTABLE] Update middleware error handling so that the `next` function never throws and instead handles any middleware errors at the proper `ErrorBoundary` and returns the `Response` up through the ancestor `next` function

docs/how-to/middleware.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -393,19 +393,31 @@ const authMiddleware = async ({ request, context }) => {
393393

394394
### `next()` and Error Handling
395395

396-
`next()` is not designed to throw errors under normal conditions, so you generally shouldn't find yourself wrapping `next` in a `try`/`catch`. The responsibility of the `next()` function is to return a `Response` for the current `Request`, so as long as that can be completed, `next()` will return the `Response` and won't `throw`. Even if a `loader` throws an error, or a component fails to render, React Router already handles those by rendering the nearest `ErrorBoundary`, so a `Response` is still generated without issue and propagated up through `next()`.
396+
React Router contains built-in error handling via the route [`ErrorBoundary`](../start/framework/route-module#errorboundary) export. Just like when a loader/acton throws, if a middleware throws an error it will be caught and handled at the appropriate `ErrorBoundary` and the `Response` will be returned through the ancestor `next()` call. This means that the `next()` function should never throw and should always return a `Response`, so you don't need to worry about wrapping it in a try/catch.
397397

398-
This behavior is important to allow middleware patterns such as automatically setting required headers on outgoing responses (i.e., committing a session) from a root middleware. If any error from a loader/action/render caused `next()` to `throw`, we'd miss the execution of ancestor middlewares on the way out and those required headers wouldn't be set.
398+
This behavior is important to allow middleware patterns such as automatically setting required headers on outgoing responses (i.e., committing a session) from a root middleware. If any error from a middleware caused `next()` to `throw`, we'd miss the execution of ancestor middlewares on the way out and those required headers wouldn't be set.
399399

400-
The only cases in which `next()` _should_ throw are if we fail to generate a `Response`. There's a few ways in which this could happen:
401-
402-
- A middleware can short circuit the rest of the request and throw a `Response` (usually a `redirect`)
403-
- If the logic directly inside of a middleware function throws, that will cause the ancestor `next()` function to throw
404-
405-
If a middleware _does_ throw (and is not caught by an ancestor middleware), then ancestor middlewares will be skipped, but React Router will still attempt to render that error in an `ErrorBoundary`.
400+
```tsx
401+
// routes/parent.tsx
402+
export const unstable_middleware = [
403+
async (_, next) => {
404+
let res = await next();
405+
// ^ res.status = 500
406+
// This response contains the ErrorBoundary
407+
return res;
408+
}
409+
]
406410

407-
- If the error threw before `next()` was called, then we don't have `loaderData` for any routes so we'll bubble to the first `ErrorBoundary` at or above all `loader` functions
408-
- If the error threw after `next()` was called, then we will bubble to the nearest `ErrorBoundary` from the throwing route
411+
// routes/parent.child.tsx
412+
export const unstable_middleware = [
413+
async (_, next) => {
414+
let res = await next();
415+
// ^ res.status = 200
416+
// This response contains the successful UI render
417+
throw new Error('Uh oh, something went wrong!)
418+
}
419+
]
420+
```
409421
410422
## Changes to `getLoadContext`/`AppLoadContext`
411423

0 commit comments

Comments
 (0)