Skip to content

Commit 4c2ce49

Browse files
committed
fix: handle SSR errors in beforeLoad/loader during client hydration
- Add generic error handling to existing redirect/notFound logic; rename handleRedirectAndNotFound to handleRouteError for clarity - Add 'invariantSource' to distinguish missing notFoundComponent invariant error from generic errors
1 parent b84e5c7 commit 4c2ce49

File tree

2 files changed

+37
-19
lines changed

2 files changed

+37
-19
lines changed

packages/router-core/src/not-found.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ export function notFound(options: NotFoundError = {}) {
2727
export function isNotFound(obj: any): obj is NotFoundError {
2828
return !!obj?.isNotFound
2929
}
30+
31+
export function isVariantNotFoundError(error: any) {
32+
return (
33+
error &&
34+
typeof error === 'object' &&
35+
'invariantSource' in error &&
36+
error.invariantSource === 'notFound'
37+
)
38+
}

packages/router-core/src/router.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
trimPathLeft,
2525
trimPathRight,
2626
} from './path'
27-
import { isNotFound } from './not-found'
27+
import { isNotFound, isVariantNotFoundError } from './not-found'
2828
import { setupScrollRestoration } from './scroll-restoration'
2929
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
3030
import { rootRouteId } from './root'
@@ -2251,14 +2251,16 @@ export class RouterCore<
22512251
return !!(allPreload && !this.state.matches.find((d) => d.id === matchId))
22522252
}
22532253

2254-
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
2254+
const handleRouteError = (match: AnyRouteMatch, err: any) => {
22552255
if (isResolvedRedirect(err)) {
22562256
if (!err.reloadDocument) {
22572257
throw err
22582258
}
22592259
}
22602260

2261-
if (isRedirect(err) || isNotFound(err)) {
2261+
const isError =
2262+
err && err instanceof Error && !isVariantNotFoundError(err)
2263+
if (isRedirect(err) || isNotFound(err) || isError) {
22622264
updateMatch(match.id, (prev) => ({
22632265
...prev,
22642266
status: isRedirect(err)
@@ -2272,8 +2274,8 @@ export class RouterCore<
22722274
loaderPromise: undefined,
22732275
}))
22742276

2275-
if (!(err as any).routeId) {
2276-
;(err as any).routeId = match.routeId
2277+
if (!err.routeId) {
2278+
err.routeId = match.routeId
22772279
}
22782280

22792281
match.beforeLoadPromise?.resolve()
@@ -2293,6 +2295,11 @@ export class RouterCore<
22932295
match: this.getMatch(match.id)!,
22942296
})
22952297
throw err
2298+
} else if (isError) {
2299+
this.serverSsr?.onMatchSettled({
2300+
router: this,
2301+
match: this.getMatch(match.id)!,
2302+
})
22962303
}
22972304
}
22982305
}
@@ -2318,13 +2325,13 @@ export class RouterCore<
23182325

23192326
err.routerCode = routerCode
23202327
firstBadMatchIndex = firstBadMatchIndex ?? index
2321-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2328+
handleRouteError(this.getMatch(matchId)!, err)
23222329

23232330
try {
23242331
route.options.onError?.(err)
23252332
} catch (errorHandlerErr) {
23262333
err = errorHandlerErr
2327-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2334+
handleRouteError(this.getMatch(matchId)!, err)
23282335
}
23292336

23302337
updateMatch(matchId, (prev) => {
@@ -2523,7 +2530,7 @@ export class RouterCore<
25232530
await prevLoaderPromise
25242531
const match = this.getMatch(matchId)!
25252532
if (match.error) {
2526-
handleRedirectAndNotFound(match, match.error)
2533+
handleRouteError(match, match.error)
25272534
}
25282535
} else {
25292536
const parentMatchPromise = matchPromises[index - 1] as any
@@ -2643,10 +2650,7 @@ export class RouterCore<
26432650
const loaderData =
26442651
await route.options.loader?.(getLoaderContext())
26452652

2646-
handleRedirectAndNotFound(
2647-
this.getMatch(matchId)!,
2648-
loaderData,
2649-
)
2653+
handleRouteError(this.getMatch(matchId)!, loaderData)
26502654

26512655
// Lazy option can modify the route options,
26522656
// so we need to wait for it to resolve before
@@ -2675,13 +2679,13 @@ export class RouterCore<
26752679

26762680
await potentialPendingMinPromise()
26772681

2678-
handleRedirectAndNotFound(this.getMatch(matchId)!, e)
2682+
handleRouteError(this.getMatch(matchId)!, e)
26792683

26802684
try {
26812685
route.options.onError?.(e)
26822686
} catch (onErrorError) {
26832687
error = onErrorError
2684-
handleRedirectAndNotFound(
2688+
handleRouteError(
26852689
this.getMatch(matchId)!,
26862690
onErrorError,
26872691
)
@@ -2710,7 +2714,7 @@ export class RouterCore<
27102714
}))
27112715
executeHead()
27122716
})
2713-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2717+
handleRouteError(this.getMatch(matchId)!, err)
27142718
}
27152719
}
27162720

@@ -3088,10 +3092,15 @@ export class RouterCore<
30883092
}
30893093

30903094
// Ensure we have a notFoundComponent
3091-
invariant(
3092-
routeCursor.options.notFoundComponent,
3093-
'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
3094-
)
3095+
// generic Error instead of a NotFoundError when notFoundComponent doesn't exist, is this a bug?
3096+
try {
3097+
invariant(
3098+
routeCursor.options.notFoundComponent,
3099+
'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
3100+
)
3101+
} catch (error) {
3102+
;(error as any).invariantSource = 'notFound'
3103+
}
30953104

30963105
// Find the match for this route
30973106
const matchForRoute = matchesByRouteId[routeCursor.id]

0 commit comments

Comments
 (0)