Skip to content

Commit 9faa690

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 9faa690

File tree

2 files changed

+42
-19
lines changed

2 files changed

+42
-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: 33 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,21 @@ 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+
console.log({
2264+
isRedirect: isRedirect(err),
2265+
isNotFound: isNotFound(err),
2266+
isError,
2267+
})
2268+
if (isRedirect(err) || isNotFound(err) || isError) {
22622269
updateMatch(match.id, (prev) => ({
22632270
...prev,
22642271
status: isRedirect(err)
@@ -2272,8 +2279,8 @@ export class RouterCore<
22722279
loaderPromise: undefined,
22732280
}))
22742281

2275-
if (!(err as any).routeId) {
2276-
;(err as any).routeId = match.routeId
2282+
if (!err.routeId) {
2283+
err.routeId = match.routeId
22772284
}
22782285

22792286
match.beforeLoadPromise?.resolve()
@@ -2293,6 +2300,11 @@ export class RouterCore<
22932300
match: this.getMatch(match.id)!,
22942301
})
22952302
throw err
2303+
} else if (isError) {
2304+
this.serverSsr?.onMatchSettled({
2305+
router: this,
2306+
match: this.getMatch(match.id)!,
2307+
})
22962308
}
22972309
}
22982310
}
@@ -2318,13 +2330,13 @@ export class RouterCore<
23182330

23192331
err.routerCode = routerCode
23202332
firstBadMatchIndex = firstBadMatchIndex ?? index
2321-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2333+
handleRouteError(this.getMatch(matchId)!, err)
23222334

23232335
try {
23242336
route.options.onError?.(err)
23252337
} catch (errorHandlerErr) {
23262338
err = errorHandlerErr
2327-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2339+
handleRouteError(this.getMatch(matchId)!, err)
23282340
}
23292341

23302342
updateMatch(matchId, (prev) => {
@@ -2523,7 +2535,7 @@ export class RouterCore<
25232535
await prevLoaderPromise
25242536
const match = this.getMatch(matchId)!
25252537
if (match.error) {
2526-
handleRedirectAndNotFound(match, match.error)
2538+
handleRouteError(match, match.error)
25272539
}
25282540
} else {
25292541
const parentMatchPromise = matchPromises[index - 1] as any
@@ -2643,10 +2655,7 @@ export class RouterCore<
26432655
const loaderData =
26442656
await route.options.loader?.(getLoaderContext())
26452657

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

26512660
// Lazy option can modify the route options,
26522661
// so we need to wait for it to resolve before
@@ -2675,13 +2684,13 @@ export class RouterCore<
26752684

26762685
await potentialPendingMinPromise()
26772686

2678-
handleRedirectAndNotFound(this.getMatch(matchId)!, e)
2687+
handleRouteError(this.getMatch(matchId)!, e)
26792688

26802689
try {
26812690
route.options.onError?.(e)
26822691
} catch (onErrorError) {
26832692
error = onErrorError
2684-
handleRedirectAndNotFound(
2693+
handleRouteError(
26852694
this.getMatch(matchId)!,
26862695
onErrorError,
26872696
)
@@ -2710,7 +2719,7 @@ export class RouterCore<
27102719
}))
27112720
executeHead()
27122721
})
2713-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2722+
handleRouteError(this.getMatch(matchId)!, err)
27142723
}
27152724
}
27162725

@@ -3088,10 +3097,15 @@ export class RouterCore<
30883097
}
30893098

30903099
// 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-
)
3100+
// generic Error instead of a NotFoundError when notFoundComponent doesn't exist, is this a bug?
3101+
try {
3102+
invariant(
3103+
routeCursor.options.notFoundComponent,
3104+
'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
3105+
)
3106+
} catch (error) {
3107+
;(error as any).invariantSource = 'notFound'
3108+
}
30953109

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

0 commit comments

Comments
 (0)