diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts
index 37bc0f45b7..908d60653f 100644
--- a/packages/nuxt/src/app/nuxt.ts
+++ b/packages/nuxt/src/app/nuxt.ts
@@ -20,7 +20,7 @@ import type { LoadingIndicator } from '../app/composables/loading-indicator'
import type { RouteAnnouncer } from '../app/composables/route-announcer'
// @ts-expect-error virtual file
-import { appId, multiApp } from '#build/nuxt.config.mjs'
+import { appId, chunkErrorEvent, multiApp } from '#build/nuxt.config.mjs'
import type { NuxtAppLiterals } from '#app'
@@ -370,12 +370,14 @@ export function createNuxtApp (options: CreateOptions) {
defineGetter(nuxtApp.vueApp, '$nuxt', nuxtApp)
defineGetter(nuxtApp.vueApp.config.globalProperties, '$nuxt', nuxtApp)
- // Listen to chunk load errors
if (import.meta.client) {
- window.addEventListener('nuxt.preloadError', (event) => {
- nuxtApp.callHook('app:chunkError', { error: (event as Event & { payload: Error }).payload })
- })
-
+ // Listen to chunk load errors
+ if (chunkErrorEvent) {
+ window.addEventListener(chunkErrorEvent, (event) => {
+ nuxtApp.callHook('app:chunkError', { error: (event as Event & { payload: Error }).payload })
+ event.preventDefault()
+ })
+ }
window.useNuxtApp = window.useNuxtApp || useNuxtApp
// Log errors captured when running plugins, in the `app:created` and `app:beforeMount` hooks
diff --git a/packages/nuxt/src/app/plugins/chunk-reload.client.ts b/packages/nuxt/src/app/plugins/chunk-reload.client.ts
index e8eb5e2e48..c43d433428 100644
--- a/packages/nuxt/src/app/plugins/chunk-reload.client.ts
+++ b/packages/nuxt/src/app/plugins/chunk-reload.client.ts
@@ -26,7 +26,7 @@ export default defineNuxtPlugin({
})
router.onError((error, to) => {
- if (chunkErrors.has(error)) {
+ if (chunkErrors.has(error) || error.message.includes('Failed to fetch dynamically imported module')) {
reloadAppAtPath(to)
}
})
diff --git a/packages/nuxt/src/core/templates.ts b/packages/nuxt/src/core/templates.ts
index 7b1845cb4a..759d070562 100644
--- a/packages/nuxt/src/core/templates.ts
+++ b/packages/nuxt/src/core/templates.ts
@@ -516,6 +516,7 @@ export const nuxtConfigTemplate: NuxtTemplate = {
`export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`,
`export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`,
`export const multiApp = ${!!ctx.nuxt.options.future.multiApp}`,
+ `export const chunkErrorEvent = ${ctx.nuxt.options.experimental.emitRouteChunkError ? ctx.nuxt.options.builder === '@nuxt/vite-builder' ? '"vite:preloadError"' : '"nuxt:preloadError"' : 'false'}`,
].join('\n\n')
},
}
diff --git a/packages/vite/src/client.ts b/packages/vite/src/client.ts
index c9e8518a90..1821e21d38 100644
--- a/packages/vite/src/client.ts
+++ b/packages/vite/src/client.ts
@@ -11,7 +11,6 @@ import { defu } from 'defu'
import { env, nodeless } from 'unenv'
import { appendCorsHeaders, appendCorsPreflightHeaders, defineEventHandler } from 'h3'
import type { ViteConfig } from '@nuxt/schema'
-import { chunkErrorPlugin } from './plugins/chunk-error'
import type { ViteBuildContext } from './vite'
import { devStyleSSRPlugin } from './plugins/dev-ssr-css'
import { runtimePathsPlugin } from './plugins/paths'
@@ -167,11 +166,6 @@ export async function buildClient (ctx: ViteBuildContext) {
clientConfig.server!.hmr = false
}
- // Emit chunk errors if the user has opted in to `experimental.emitRouteChunkError`
- if (ctx.nuxt.options.experimental.emitRouteChunkError) {
- clientConfig.plugins!.push(chunkErrorPlugin({ sourcemap: !!ctx.nuxt.options.sourcemap.client }))
- }
-
// Inject an h3-based CORS handler in preference to vite's
const useViteCors = clientConfig.server?.cors !== undefined
if (!useViteCors) {
diff --git a/packages/vite/src/plugins/chunk-error.ts b/packages/vite/src/plugins/chunk-error.ts
deleted file mode 100644
index 4502435978..0000000000
--- a/packages/vite/src/plugins/chunk-error.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import MagicString from 'magic-string'
-import type { Plugin } from 'vite'
-
-const vitePreloadHelperId = '\0vite/preload-helper'
-
-// TODO: remove this function when we upgrade to vite 5
-export function chunkErrorPlugin (options: { sourcemap?: boolean }): Plugin {
- return {
- name: 'nuxt:chunk-error',
- transform (code, id) {
- // Vite 5 has an id with extension
- if (!(id === vitePreloadHelperId || id === `${vitePreloadHelperId}.js`) || code.includes('nuxt.preloadError')) { return }
-
- const s = new MagicString(code)
- s.replace(/__vitePreload/g, '___vitePreload')
- s.append(`
-export const __vitePreload = (...args) => ___vitePreload(...args).catch(err => {
- const e = new Event("nuxt.preloadError");
- e.payload = err;
- window.dispatchEvent(e);
- throw err;
-})`)
-
- return {
- code: s.toString(),
- map: options.sourcemap
- ? s.generateMap({ hires: true })
- : undefined,
- }
- },
- }
-}
diff --git a/packages/webpack/src/plugins/chunk.ts b/packages/webpack/src/plugins/chunk.ts
index 4832aeb941..56edb4c2fc 100644
--- a/packages/webpack/src/plugins/chunk.ts
+++ b/packages/webpack/src/plugins/chunk.ts
@@ -7,11 +7,11 @@ const script = `
if (typeof ${webpack.RuntimeGlobals.require} !== "undefined") {
var _ensureChunk = ${webpack.RuntimeGlobals.ensureChunk};
${webpack.RuntimeGlobals.ensureChunk} = function (chunkId) {
- return Promise.resolve(_ensureChunk(chunkId)).catch(err => {
- const e = new Event("nuxt.preloadError");
- e.payload = err;
- window.dispatchEvent(e);
- throw err;
+ return Promise.resolve(_ensureChunk(chunkId)).catch(error => {
+ const e = new Event('nuxt:preloadError', { cancelable: true })
+ e.payload = error
+ window.dispatchEvent(e)
+ throw error
});
};
};`
diff --git a/test/basic.test.ts b/test/basic.test.ts
index 5c99fa652b..fb68b45020 100644
--- a/test/basic.test.ts
+++ b/test/basic.test.ts
@@ -1164,14 +1164,15 @@ describe('errors', () => {
})
// TODO: need to create test for webpack
- it.runIf(!isDev() && !isWebpack)('should handle chunk loading errors', async () => {
+ it.runIf(!isDev())('should handle chunk loading errors', async () => {
const { page, consoleLogs } = await renderPage('/')
await page.getByText('Increment state').click()
await page.getByText('Increment state').click()
expect(await page.innerText('div')).toContain('Some value: 3')
+ await page.route(/.*/, route => route.abort('timedout'), { times: 1 })
await page.getByText('Chunk error').click()
await page.waitForURL(url('/chunk-error'))
- expect(consoleLogs.map(c => c.text).join('')).toContain('caught chunk load error')
+ expect(consoleLogs.map(c => c.text).join('')).toContain('Failed to load resource')
expect(await page.innerText('div')).toContain('Chunk error page')
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/chunk-error')
await page.locator('div').getByText('State: 3').waitFor()
diff --git a/test/fixtures/basic/pages/chunk-error.vue b/test/fixtures/basic/pages/chunk-error.vue
index 7c9cb9da82..ad5e561940 100644
--- a/test/fixtures/basic/pages/chunk-error.vue
+++ b/test/fixtures/basic/pages/chunk-error.vue
@@ -1,14 +1,4 @@
diff --git a/test/fixtures/basic/pages/index.vue b/test/fixtures/basic/pages/index.vue
index 5c59090757..3723544e03 100644
--- a/test/fixtures/basic/pages/index.vue
+++ b/test/fixtures/basic/pages/index.vue
@@ -36,8 +36,8 @@
Immediate remove unmounted
Chunk error
diff --git a/test/fixtures/basic/plugins/chunk-error.ts b/test/fixtures/basic/plugins/chunk-error.ts
deleted file mode 100644
index e23e5ecdd9..0000000000
--- a/test/fixtures/basic/plugins/chunk-error.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export default defineNuxtPlugin((nuxtApp) => {
- nuxtApp.hook('app:chunkError', () => {
- console.log('caught chunk load error')
- })
-})