How we can handle "failed to fetch dynamically imported module" error in Vue 3 ?
Handle dynamically imported module error with explanation and providing a reusable approach for vue.js applications.
If you’re a front-end developer and focused on improving performance in your Vue.js
app, you might choose to use defineAsyncComponent
to load components only when needed.
However, many developers attempting this approach encounter the following issue:
Failed to fetch dynamically imported module
Why we get this error?
If your application is properly configured and the module path is ok, the most likely reason is a network issue.
Clients with poor internet connections, server downtime, or offline devices might experience timeout
or failed request
errors, preventing the dynamic module from loading.
You might see this error on the Browser's console
or Sentry
reports.
How we can handle that
When a user frequently visits a website and is not visiting the page for the first time, caching strategies (like using service workers) can help prevent this error by serving previously cached modules.
However, if it’s the user’s first visit, the error might still occur due to network issues. In this case, a retry strategy can be a good fallback to reattempt loading the module.
In this guide we will implement the retry strategy by using defineAsyncComponent
and its options to load dynamic modules.
Let's see how we can handle that in a easiest way possible in Vue 3:
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
const MAX_RETRY_COUNT = 7;
const RETRY_DELAY_MS = 500;
const AsyncComponent = defineAsyncComponent({
loader: () => import('./MyComponent.vue'),
onError(_, retry, fail, attempts) {
if (attempts > MAX_RETRY_COUNT) fail(); // Call fail() after maximum retries
else setTimeout(retry, RETRY_DELAY_MS); // Retry with delay
},
})
</script>
<template>
<AsyncComponent />
</template>
Reusable Approach
Now let's write a more practical and reusable version of that, that has default values.
import { defineAsyncComponent } from 'vue'
import type { Component, DefineComponent, AsyncComponentOptions } from 'vue'
// Type for loader / import()
type ComponentLoader = () => Promise<{ default: Component }>
// Options with extended retry fields
interface Options extends Omit<AsyncComponentOptions, 'loader'> {
maxRetries?: number
retryDelay?: number
}
export function createAsyncComponent(loader: ComponentLoader, options: Options = {}): DefineComponent {
// Destructuring with default values
const { maxRetries = 3, timeout = 30000, retryDelay = 1000, ...restOptions } = options
return defineAsyncComponent({
...restOptions,
loader,
timeout,
onError(error, retry, fail, attempts) {
// log attempt number and error
console.warn(`Async component load attempt ${attempts} failed:`, error)
// Fail if max retries exceeded
if (attempts > maxRetries) {
console.error('Max retries exceeded. Component load failed.')
fail()
return
}
// Exponential backoff with jitter
const baseDelay = retryDelay * Math.pow(2, attempts - 1)
// add randomness to retry delay
const jitteredDelay = baseDelay * (1 + Math.random() * 0.5)
// Schedule to call retry
setTimeout(retry, jitteredDelay)
},
}) as DefineComponent
}
Jitter can add random factor to our retry strategy.
Now let's use our custom async component creator:
<script>
// With Defaults
const AsyncComp1 = createAsyncComponent(() => import('@/components/Heavy.vue'))
// With custom attempt number
const AsyncComp2 = createAsyncComponent(() => import('../components/Big.vue'), { maxRetries: 6 })
// With custom error component
const AsyncComp2 = createAsyncComponent(() => import('@/components/Bad.vue'), { errorComponent: MyErrComp })
</script>
Conclusion
In here we learned how we can handle failed to fetch..
error by using retry strategy to retry loading async components in vue 3. I highly recommend this article to learn more about defineAsyncComponent
please let me know if you use any other approach or see any improvements. 🙌