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.

6 min read

November 24th, 2024

Nick Mousavi

If you’re a front-end developer and focused on improving performance in your

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

and its options to load dynamic modules.

Also there is a

on Github that handles this issue in
React
.

Let's see how we can handle that in a easiest way possible in Vue 3:

RetryComponent.vue
<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.

utils/createAsyncComponent.ts
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:

Usage.vue
<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. 🙌

Tags:VueNuxt