Back to stories
<Frontend/>

Lazy Loading in Vue: defineAsyncComponent and Code Splitting

Share by

Lazy Loading in Vue: defineAsyncComponent and Code Splitting

Loading every component up front can make the initial bundle large and slow. Lazy loading defers loading a component until it's needed (e.g. when a route is opened or a tab is shown). Vue's defineAsyncComponent and dynamic import() make this straightforward and integrate with your bundler's code splitting. This post shows how.


Why lazy load

  • Smaller initial bundle: Heavy charts, editors, or modals are not downloaded until the user needs them.
  • Faster first paint: Less JavaScript to parse and execute on first load.
  • Better perceived performance: The app becomes interactive sooner; heavy screens load on demand.

defineAsyncComponent and dynamic import

Vue's defineAsyncComponent accepts a function that returns a Promise of a component. The natural way to get that Promise is a dynamic import: () => import('./Heavy.vue'). Your bundler (Vite, Webpack, etc.) will split that into a separate chunk and load it when the import runs.

Basic usage:

import { defineAsyncComponent } from 'vue'

const HeavyChart = defineAsyncComponent(
  () => import('@/components/HeavyChart.vue')
)

Use HeavyChart like any other component. The first time it's rendered, the chunk is fetched and the component is shown when ready.


Loading and error states

You can pass an options object to show a loading component or handle errors:

const HeavyChart = defineAsyncComponent({
  loader: () => import('@/components/HeavyChart.vue'),
  loadingComponent: SpinnerComponent,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 10000,
})
  • loadingComponent: Shown while the chunk is loading.
  • delay: Wait this many ms before showing the loading component (avoids flash for fast loads).
  • timeout: If loading takes longer, show errorComponent.
  • errorComponent: Shown on load failure or timeout.

When to use it

  • Routes: Lazy load route-level components so each page is a separate chunk (e.g. in Vue Router's component: () => import('./views/Admin.vue')).
  • Heavy UI: Modals, drawers, charts, or editors that appear only in certain flows.
  • Below-the-fold or tabbed content: Load the content of a tab or section when the user switches to it.

Avoid lazy loading tiny components used everywhere; the extra request and delay aren't worth it.


Summary

  • Lazy loading defers loading a component until it's needed, shrinking the initial bundle and speeding first load.
  • Use defineAsyncComponent with dynamic import so Vue and your bundler handle async loading and code splitting.
  • Add loadingComponent, errorComponent, and timeout for a better UX while the chunk loads or if it fails.
  • Prefer lazy loading for routes and heavy, conditionally-rendered components; keep small, critical components in the main bundle.