Skip to content

Commit dc417ac

Browse files
authored
fix: handle chunk errors (#357)
1 parent 4e541b5 commit dc417ac

File tree

5 files changed

+90
-39
lines changed

5 files changed

+90
-39
lines changed

.changeset/calm-deer-wave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vocs": patch
3+
---
4+
5+
Added handling for "Failed to fetch dynamically imported module" errors, which can be caused by version skew.

src/app/index.client.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { hydrateRoot } from 'react-dom/client'
55
import { createBrowserRouter, RouterProvider } from 'react-router'
66
import { ConfigProvider, getConfig } from './hooks/useConfig.js'
77
import { routes } from './routes.js'
8+
import { clearChunkReloadFlag } from './utils/chunkError.js'
89
import { hydrateLazyRoutes } from './utils/hydrateLazyRoutes.js'
910
import { removeTempStyles } from './utils/removeTempStyles.js'
1011

@@ -15,6 +16,7 @@ async function hydrate() {
1516

1617
await hydrateLazyRoutes(routes, basePath)
1718
removeTempStyles()
19+
clearChunkReloadFlag()
1820

1921
const router = createBrowserRouter(routes, { basename: basePath })
2022
hydrateRoot(

src/app/routes.tsx

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,31 @@ import type { RouteObject } from 'react-router'
44
import { NotFound } from './components/NotFound.js'
55
import { DocsLayout } from './layouts/DocsLayout.js'
66
import { Root } from './root.js'
7+
import { maybeHandleChunkError } from './utils/chunkError.js'
78

89
const notFoundRoute = (() => {
910
const virtualRoute = routes_virtual.find(({ path }) => path === '*')
1011
if (virtualRoute)
1112
return {
1213
path: virtualRoute.path,
1314
lazy: async () => {
14-
const { frontmatter, ...route } = await virtualRoute.lazy()
15+
try {
16+
const { frontmatter, ...route } = await virtualRoute.lazy()
1517

16-
return {
17-
...route,
18-
element: (
19-
<Root frontmatter={frontmatter} path={virtualRoute.path}>
20-
<DocsLayout>
21-
<route.default />
22-
</DocsLayout>
23-
</Root>
24-
),
25-
} satisfies RouteObject
18+
return {
19+
...route,
20+
element: (
21+
<Root frontmatter={frontmatter} path={virtualRoute.path}>
22+
<DocsLayout>
23+
<route.default />
24+
</DocsLayout>
25+
</Root>
26+
),
27+
} satisfies RouteObject
28+
} catch (error) {
29+
maybeHandleChunkError(error as Error)
30+
throw error
31+
}
2632
},
2733
}
2834

@@ -45,24 +51,29 @@ export const routes = [
4551
.map((route_virtual) => ({
4652
path: route_virtual.path,
4753
lazy: async () => {
48-
const { frontmatter, ...route } = await route_virtual.lazy()
54+
try {
55+
const { frontmatter, ...route } = await route_virtual.lazy()
4956

50-
return {
51-
...route,
52-
element: (
53-
<Root
54-
content={decodeURIComponent(route_virtual.content ?? '')}
55-
filePath={route_virtual.filePath}
56-
frontmatter={frontmatter}
57-
lastUpdatedAt={route_virtual.lastUpdatedAt}
58-
path={route_virtual.path}
59-
>
60-
<DocsLayout>
61-
<route.default />
62-
</DocsLayout>
63-
</Root>
64-
),
65-
} satisfies RouteObject
57+
return {
58+
...route,
59+
element: (
60+
<Root
61+
content={decodeURIComponent(route_virtual.content ?? '')}
62+
filePath={route_virtual.filePath}
63+
frontmatter={frontmatter}
64+
lastUpdatedAt={route_virtual.lastUpdatedAt}
65+
path={route_virtual.path}
66+
>
67+
<DocsLayout>
68+
<route.default />
69+
</DocsLayout>
70+
</Root>
71+
),
72+
} satisfies RouteObject
73+
} catch (error) {
74+
maybeHandleChunkError(error as Error)
75+
throw error
76+
}
6677
},
6778
})),
6879
notFoundRoute,

src/app/utils/chunkError.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export function isChunkError(error: Error) {
2+
if (!error) return false
3+
if (!error.message) return false
4+
const message = error.message.toLowerCase()
5+
return (
6+
message.includes('failed to fetch dynamically imported module') || // Chrome
7+
message.includes('error loading dynamically imported module') || // Firefox/Safari
8+
message.includes('dynamically imported module') // fallback
9+
)
10+
}
11+
12+
export function maybeHandleChunkError(error: Error) {
13+
if (!isChunkError(error)) return
14+
if (sessionStorage.getItem(reloadKey)) {
15+
sessionStorage.removeItem(reloadKey)
16+
return
17+
}
18+
sessionStorage.setItem(reloadKey, 'true')
19+
window.location.reload()
20+
}
21+
22+
export function clearChunkReloadFlag() {
23+
sessionStorage.removeItem(reloadKey)
24+
}
25+
26+
const reloadKey = 'vocs.reload'

src/app/utils/hydrateLazyRoutes.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import { matchRoutes, type RouteObject } from 'react-router'
22

3+
import { maybeHandleChunkError } from './chunkError.js'
4+
35
export async function hydrateLazyRoutes(routes: RouteObject[], basePath: string | undefined) {
46
// Determine if any of the initial routes are lazy
57
const lazyMatches = matchRoutes(routes, window.location, basePath)?.filter((m) => m.route.lazy)
68

79
// Load the lazy matches and update the routes before creating your router
810
// so we can hydrate the SSR-rendered content synchronously
9-
if (lazyMatches && lazyMatches?.length > 0) {
10-
await Promise.all(
11-
lazyMatches.map(async (m) => {
12-
const routeModule = typeof m.route.lazy === 'function' ? await m.route.lazy() : m.route.lazy
13-
Object.assign(m.route, {
14-
...routeModule,
15-
lazy: undefined,
16-
})
17-
}),
18-
)
19-
}
11+
if (lazyMatches && lazyMatches?.length > 0)
12+
try {
13+
await Promise.all(
14+
lazyMatches.map(async (m) => {
15+
const routeModule =
16+
typeof m.route.lazy === 'function' ? await m.route.lazy() : m.route.lazy
17+
Object.assign(m.route, {
18+
...routeModule,
19+
lazy: undefined,
20+
})
21+
}),
22+
)
23+
} catch (error) {
24+
maybeHandleChunkError(error as Error)
25+
throw error
26+
}
2027
}

0 commit comments

Comments
 (0)