router: Suspense doesn't display fallback content and warning is output

Version

3.0.2

Reproduction link

https://jsfiddle.net/seijikohara/jmw3rpue/

Steps to reproduce

No fallback content is displayed and warning is output. The version of Vue is 3.0.2.

[Vue warn]: <Suspense> slots expect a single root node.

<router-view v-slot="{ Component }">
  <Suspense>
    <template #default>
      <component :is="Component" />
    </template>
    <template #fallback>
      <span>Loading...</span>
    </template>
  </Suspense>
</router-view>

What is expected?

Show fallback contents until the component of async setup is initialized.

What is actually happening?

No fallback content is displayed and warning is output.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

@seijikohara You may need to add a timeout on your suspense declaration (since changes in Vue 3.0.0-rc.12, see the discussion in https://github.com/vuejs/vue-next/issues/2142).

Something like <Suspense timeout="0"> should work to display the fallback content.

The warning is indeed painful, and is an issue in vue-next @LinusBorg I wanted to fix it and opened https://github.com/vuejs/vue-next/pull/2337 but I need some help to finish the PR.

@cexbrayat

Something like <Suspense timeout="0"> should work to display the fallback content.

I added timeout="0" to Suspense and the fallback was displayed. Thanks. https://jsfiddle.net/zmpvrxq4/

<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Transition mode="out-in">
      <KeepAlive>
        <Suspense>
          <!-- 主要内容 -->
          <component :is="Component"></component>

          <!-- 加载中状态 -->
          <template #fallback>
            正在加载...
          </template>
        </Suspense>
      </KeepAlive>
    </Transition>
  </template>
</RouterView>

Add this works for me.

<template v-if="Component">
...
</template>

I fixed this issue with the wrapping of the <Component /> in <div> and it’s work for me.

  <router-view v-slot="{ Component }">
    <suspense timeout="0">
      <div>
        <component :is="Component"></component>
      </div>
      <template #fallback>
        <div>
          Loading...
        </div>
      </template>
    </suspense>
  </router-view>

this works well for me after many tries

    <RouterView name="default" v-slot="{ Component, route }">
      <transition :name="route.meta.transition" mode="out-in" :duration="300" :key="route.path">
        <Suspense >
          <template #default>
            <component :is="Component" :key="route.path"/>
          </template>
          <template #fallback>
            <div>
              Loading...
            </div>
          </template>
        </Suspense>
      </transition>
    </RouterView>

Although it re-mounts with every route request. any cleaner ideas!

The best way to use RouterView, Suspense, Transition and KeepAlive together is as mentioned on the Vue Docs site under the Suspense Component page

I could fixed the problem by implementing <Suspense timeout="0"> and by importing { defineAsyncComponent } from vue in the router.js file

// @/router.js

import { createRouter, createWebHistory } from "vue-router";
import { defineAsyncComponent } from "vue";

export const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: "/",
      name: "Main",
      component: defineAsyncComponent(() => import("@/views/Main.vue")),
    }
  ]
})

And in the App.vue file:

<template>
  <router-view v-slot="{ Component }">
    <suspense timeout="0">
      <!-- Default -->
      <template #default>
        <component :is="Component" />
      </template>

      <!-- Loading -->
      <template #fallback>
        <Loading />
      </template>
    </suspense>
  </router-view>
</template>

<script>
import Loading from "@/views/Loading.vue";

export default {
  components: {
    Loading
  }
};
</script>

And it works like charm ✨

The only drawback of this solution is that you get the following warning message prompted in the console:

vue-router.esm-bundler.js:72 [Vue Router warn]: Component "default" in record with path "/" is defined using "defineAsyncComponent()". Write "() => import('./MyPage.vue')" instead of "defineAsyncComponent(() => import('./MyPage.vue'))".

Does someone know how to prevent this warning message from being prompted?

Thanks 🙂