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

Also there is a gist 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
Nick Mousavi
A small step forward is still progress.

To get in touch with Nick Mousavi,
please enter your email address below.

© 2020-present Nick Mousavi. All Rights Reserved.

Privacy Policy