I'm using Suspense, and fetching async data on pages. On init fallback loading indicator is visible, but while switching pages I'm keeping current page content until new page is ready + nprogress
bar, so site behaves more like SSR. There is also Transition between, but it's not important for that issue.
And it works great. Problem is when my page has child pages (nested routes), also with Suspense. When I'm on nested page like /demo/demo1
and navigate to home, content of /demo
template stays like it should, but content of subpage demo1
disappears. I need to keep nested Suspense until parent Suspense is ready. Any ideas?
I simplified code as much as possible. I'm using same <RouterView>
code on both root App and page.
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense>
<component :is="Component" />
<template #fallback>
Loading...
</template>
</Suspense>
</template>
</RouterView>
Here is live reproduction: https://stackblitz.com/edit/vitejs-vite-gg6kqo?file=src/pages/demo/demo1.vue (there is no nprogress, just wait 1s between pages).
Steps to reproduction:
- Click on "About" - "home page" stays until "about" is ready, and then "about" appears - works OK.
- Click on "Demo" - "demo" root page appears, fallback for nested "demo/demo1" route appears - works OK.
- Click on "Home" - content of "demo/demo1" immediately disappears.
I wouldn't say this to be the best answer, but it seems to me that in order for
<Suspense>
to work, its child must be an asynchronous component. In this case,<RouterView>
is rendered as a non-asynchronous component (even though its children are asynchronous components), thus the root<Suspense>
wouldn't wait for the<Suspense>
inside the<RouterView>
.As a workaround, I think we can cache the component, so that it wouldn't look as if it's gone when the router is changed (the third case).
My method of caching is by adding this
<script setup>
in thedemo.vue
file:And then modify the
<RouterView>
inside the same file to this:The
{{ cacheComponent(Component) }}
line will cache the component when it's rendered (it's used that way because<RouterView>
doesn't have callback when its slots are mounted). TheCachedComponent.value = Component || CachedComponent.value;
line will update the CachedComponent value if there is a new Component that is not falsy. And if it's falsy (meaning that it's not mounted yet or not mounted anymore), it will render the previous latest cached version of the component. I know this is hacky, but in my opinion, this is a very simple workaround.This is the forked stackblitz if you're interested.