Before rendering a page for a given route, I'd like to synchronously fetch the necessary data first. Ideally, I'd like to have the data fetching within the page component, but I'm not opposed to doing it in the router files. I've read and tried various ways of doing it, but part of the challenge comes from the fact that there are also multiple ways of building components and the usage of certain features vary.
In my case, I'm building single file components using the Composition API and <script setup>
syntax. The Vue Router documentation link talks about "fetching before navigation" in which I could reach for beforeRouteEnter
or beforeRouteUpdate
, but this is shown using the Options API. They do have the page for the Composition API mentioning I could use onBeforeRouteUpdate
, but that uses the setup()
function. I figured I'd try it out anyway with <script setup>
:
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
onBeforeRouteUpdate(() => {
console.log('onBeforeRouteUpdate')
})
</script>
However, this does not execute. The closest method I've tried that works is fetching the data in the router, using the beforeEnter
guard, and setting the data onto the meta
property, which can then get accessed on the route instance in the component:
beforeEnter: (to, from, next) => {
fetch('https://pokeapi.co/api/v2/pokemon/ditto')
.then(res => res.json())
.then(res => {
to.meta.pokemon = res;
next();
});
}
But with this, which is noted in the documentation, beforeEnter
only triggers when entering the route. Params changes will not retrigger this, meaning that I'd have to set up a watcher on the route in the component anyway. I might as well just have had all this logic in the component itself.
I just can't seem to find a good way to do this, but I might have overlooked something. If anyone has some pointers or advice, I'd appreciate it. Thanks in advance.
CodePudding user response:
First off, beforeRouteUpdate
is only triggered when updating the actual route but not going to another component/page as officially told here.
An example on what could trigger that lifecycle hook would be
<button @click="$router.push({ hash: `#${Math.floor(Math.random() * 10)}` })">
random hash
</button>
onBeforeRouteLeave
perfectly works tho, as you can expect, when moving from page to page.
As for the initial question, you could implement some kind of router middleware like Nuxt does it. That way, you could await
an HTTP call and only then allow for an actual navigation. Hence creating a block navigation effect pretty much.
I'm not sure on how to write that with Composition API, but I know that it perfectly works with Options API (quite some blog posts available). setup
by itself behaving in it's own life-cycly way, I guess quite some things are rather tricky.
TLDR: a good ol' router middleware wrapper around your pages (like a layout) is the perfect combo in your case IMO. There, you could set a single watcher for quite a lot of pages at the same time.
But everything depends on how you want to organize yourself and structure your code of course.
Skeleton screens bring a sense of being faster than something blocking but overall, you could also use prefetch (coming with Nuxt too by default) to get some hints and potentially load some assets even before they are needed. ( other tricks in the same domain to speed up your network requests)
CodePudding user response:
There is a solution - using top level await - https://vuejs.org/api/sfc-script-setup.html#top-level-await
Just wrap your RouterView component in a Suspense component like shown here - https://vuejs.org/guide/built-ins/suspense.html#combining-with-other-components (don't use the components you don't need)
The only caveat is that the 'loading screen' will be visible on the initial request.
I made a little demo for you so you can try it out - https://github.com/ileue/vue-top-level-await-demo