5

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.

kissu
  • 40,416
  • 14
  • 65
  • 133
kenshin9
  • 2,215
  • 4
  • 23
  • 38
  • Do you want block the render/navigation of the page until your data is resolved or not? – kissu Oct 23 '22 at 23:47
  • Yeah, that's correct. I basically want it to function like a typical webpage that doesn't use async requests and skeleton screens. – kenshin9 Oct 23 '22 at 23:48
  • As someone zealous about render times, I would encourage that you do not do this :) rendering something, even something *unfinished*, is always preferable to blocking the app. – Leland Oct 24 '22 at 00:29
  • @Leland yep, feels faster for the users for sure. But it's also quite more work. – kissu Oct 24 '22 at 00:47
  • 1
    Yeah, I get the feeling skeletons are what's preferred today. But as kissu mentioned, it does require more work, which requires more time (the real issue really). – kenshin9 Oct 24 '22 at 04:08

2 Answers2

2

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)

kissu
  • 40,416
  • 14
  • 65
  • 133
  • Thanks for the response. Yeah, I get how beforeRouteUpdate works. I mentioned that specifically because when it comes to the Composition API, they seem to only have onBeforeRouteUpdate and onBeforeRouteLeave. And seeing as how I couldn't get that to work, I wondered if I was doing something wrong. I'd need to handle the initial load and param changes, so beforeRouteUpdate would have at least helped there. I'll check out the other ideas you mentioned, so thanks again for that. – kenshin9 Oct 24 '22 at 04:18
  • @kenshin9 yeah, I think you're right: there are only those 2 for CAPI apparently. At the end of the day, I don't think that using OAPI is a big issue overall if it's only used for the navigation guards. The configuration was quite easy and you did it well at least. For the param changes, if you have a wrapping middleware, you could set a `watch` down there to track any router changes for sure, no need to have something extra. `$route.params.search` or alike would perfectly fit IMO! – kissu Oct 24 '22 at 07:37
2

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

remonke
  • 251
  • 3
  • 6
  • Great solution also. Meanwhile, it is not **blocking** the navigation. Rather, we stay on the destination then fetch the data. – kissu Oct 24 '22 at 00:58
  • okay i have figured it out - for each page you create three components - data component, page component and link component. when link is clicked it prevents the default behavior and fetches the data then it does router push - which is exactly what you want. i have made a commit to the repo so you can try it out, though PLEASE don't ever use this. nuxt 3 in spa mode is a much more reliable option than this. – remonke Oct 24 '22 at 01:42
  • Haha, yeah quite a lot of work for something simple for sure. Kudos for the hard work trying to improve your answer! – kissu Oct 24 '22 at 01:48
  • I had been aware of top-level await and Suspense, but hadn't tried it until you mentioned it. And it doesn't work how I originally thought it would, which actually turns out to be a good thing, as I think it looks like what I'm looking for! I'll have to test it out some more in the morning though. What do you think about Suspense being an experimental feature though? This is for an important system in production. – kenshin9 Oct 24 '22 at 05:03
  • 1
    @kenshin9 it's experimental but quite safe to use already, don't worry about that one. There will be no hardcore breaking change. – kissu Oct 24 '22 at 08:06