iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Async Components

defineAsyncComponent lazily loads a component, returning a placeholder until the chunk arrives. It is Vues code-splitting primitive — use it for routes, modals, charts, anything that does not need to be in the first JS payload. Combine with Suspense for declarative loading boundaries.

Async components with loading + error states

EXAMPLE
<script setup lang='ts'>
import { defineAsyncComponent, ref } from 'vue';

// 1) Minimal: lazy import a component
const Chart = defineAsyncComponent(() => import('./HeavyChart.vue'));

// 2) Full options — fallback UI + error handler + delay before showing loading
const Editor = defineAsyncComponent({
  loader: () => import('./RichTextEditor.vue'),
  loadingComponent: () => import('./Skeleton.vue').then((m) => m.default),
  errorComponent:   () => import('./LoadFailed.vue').then((m) => m.default),
  delay: 150,           // do NOT show loading state if the chunk arrives in <150ms
  timeout: 8000,        // give up after 8s -> show errorComponent
  onError(err, retry, fail, attempts) {
    if (attempts <= 2) retry();   // auto-retry transient network errors twice
    else fail();
  },
});

// 3) Route-level code splitting with Vue Router
// const routes = [
//   { path: '/orders', component: () => import('./pages/Orders.vue') },
// ];

const showEditor = ref(false);
</script>

<template>
  <!-- 4) Suspense around async children — declarative skeleton + error boundary -->
  <Suspense>
    <template #default>
      <Chart />
    </template>
    <template #fallback>
      <Skeleton h='240px' />
    </template>
  </Suspense>

  <button @click='showEditor = true'>Open editor</button>

  <Suspense v-if='showEditor'>
    <template #default>
      <Editor />
    </template>
    <template #fallback>
      <div>Loading editor...</div>
    </template>
  </Suspense>
</template>

<!-- 5) Prefetch on idle — warm the chunk before the user clicks -->
<script setup lang='ts'>
import { onMounted } from 'vue';

onMounted(() => {
  // Modern browsers expose requestIdleCallback; fallback to setTimeout
  const idle = (window as any).requestIdleCallback ?? setTimeout;
  idle(() => import('./RichTextEditor.vue'));
});
</script>

<!-- 6) Inline Suspense + AsyncComponent is the typical 'data-fetching component' pattern -->
<!--   The async setup() in a child returns a promise; Suspense waits on it.       -->
<!--   /pages/Orders.vue                                                            -->
<!--   <script setup>                                                              -->
<!--     const orders = await fetch('/api/orders').then(r => r.json());            -->
<!--   </script>                                                                   -->

Why it matters

Set delay around 150–200 ms so fast chunks do not flash a spinner the user can barely see. Pair it with a timeout + auto-retry in onError so a transient CDN blip turns into "one retry, recovered" instead of a permanent error UI.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { defineAsyncComponent } from 'vue';
const Heavy = defineAsyncComponent(() => import('./Heavy.vue'));
Try it Yourself »

Discussion

Loading…