Skip to the solution
HardFrontend9 min read

What is Suspense in Vue?

842 views

The problem

Question presented to candidate: "Our dashboard has a profile panel and a stats panel, each loading its own data, and the page flickers through two spinners. Someone suggested Vue's <Suspense>. What is it, what can it wait for, and what would you need to handle around it?"

What a strong answer should cover:

  • <Suspense> is a built-in component that coordinates async dependencies in the subtree below it and renders one #fallback until all of them are ready, then the #default slot.
  • The two kinds of dependency: components with an async setup() (including <script setup> with top-level await) and async components from defineAsyncComponent.
  • Events: pending, fallback, resolve.
  • Suspense does not handle errors: a rejected setup must be caught with onErrorCaptured or app.config.errorHandler.
  • On the server, renderToString waits for the async setup and sends the resolved HTML, not the fallback.
  • It is still marked experimental: the Vue 3.5.44 runtime prints that on first use and the docs carry an "Experimental Feature" warning.

Clarifying questions expected:

  • "Do both panels need to appear together, or can each show as soon as it is ready?"
  • "Is the app server-rendered?"

Code / implementation expected: Yes β€” a <Suspense> around two async children with a fallback, plus error handling.

  • suspense
  • async

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 9 sections

Read the code

VueSuspense around an async setup() and an async component, with error handling and retry
<script setup>
import { ref, defineComponent, defineAsyncComponent, h, onErrorCaptured } from "vue";

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// 1) A component with an async setup(): Suspense waits for its promise.
const Profile = defineComponent({
  props: { fail: Boolean },
  async setup(props) {
    await sleep(40);
    if (props.fail) throw new Error("profile API returned 500");
    const user = { name: "Ada" };
    return () => h("p", `Profile: ${user.name}`);
  },
});

// 2) An async component: Suspense waits for its loader too.
const Stats = defineAsyncComponent(async () => {
  await sleep(80);
  return { render: () => h("p", "Stats: 42 orders") };
});

const error = ref(null);
const fail = ref(false);
const attempt = ref(0);
onErrorCaptured((err) => {
  error.value = err.message;
  return false; // handled here, do not propagate
});
const retry = (shouldFail) => {
  error.value = null;
  fail.value = shouldFail;
  attempt.value++; // new key = new Suspense, so it goes pending again
};
</script>

<template>
  <button class="ok" @click="retry(false)">Load</button>
  <button class="bad" @click="retry(true)">Load (failing)</button>
  <p v-if="error" class="error">Could not load: {{ error }}</p>
  <Suspense v-else :key="attempt" @pending="console.log('pending')" @resolve="console.log('resolve')" @fallback="console.log('fallback')">
    <section>
      <Profile :fail="fail" />
      <Stats />
    </section>
    <template #fallback>
      <p>Loading dashboard...</p>
    </template>
  </Suspense>
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 78 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextHard

How does defineAsyncComponent work with loading, error and retry in 2025?

Continue