Async Components

Lazy-Load Components — Smaller Bundles

Async Components

defineAsyncComponent loads a component on demand with loading and error fallbacks. Use it for heavy widgets the user might never open.

4 min read Level 3/5 #vue#components#async
What you'll learn
  • Wrap a dynamic import with defineAsyncComponent
  • Add loadingComponent and errorComponent
  • Combine with Suspense

Async components defer loading a component’s code until it is actually rendered. Bundlers split it into a separate chunk, so the initial download stays small.

Basic Async Component

import { defineAsyncComponent } from 'vue'

export const Heavy = defineAsyncComponent(
  () => import('./Heavy.vue')
)

Use it like any normal component. The first time it renders, Vue fetches the chunk; subsequent uses are instant.

Loading and Error Fallbacks

The options form gives you fallback components and timing knobs.

import { defineAsyncComponent } from 'vue'
import Spinner from './Spinner.vue'
import ErrorFallback from './ErrorFallback.vue'

export const Editor = defineAsyncComponent({
  loader: () => import('./Editor.vue'),
  loadingComponent: Spinner,
  errorComponent: ErrorFallback,
  delay: 200,      // wait 200ms before showing the spinner
  timeout: 5000,   // after 5s, show the error fallback
})

The delay avoids a spinner flash for fast network responses. The timeout protects against stuck loads.

Pairing With Suspense

If your async component also uses top-level await in <script setup>, wrap it in Suspense so Vue can coordinate the loading state.

<template>
  <Suspense>
    <template #default>
      <Dashboard />
    </template>
    <template #fallback>
      <p>Loading dashboard…</p>
    </template>
  </Suspense>
</template>

Suspense waits for all async dependencies in its tree before showing #default. Use it sparingly — usually one near the route level is enough.

Form Inputs — v-model Across Every Input Type →