Closed atinux closed 4 years ago
I haven't built a Nuxt app before, but I'm in the middle of teaching these topics at Vue Mastery. Here are my feelings:
This makes me wonder if you planning on keeping asyncData with the same functionality? This might be nice for those who want to upgrade to Nuxt 3 who are using it.
If your goal is to optimize for beginners, not having to deal with a loading state on the client side was quite nice. However, I read above that this is not possible?
Also, it might be nice to keep asyncData with the same functionality (of waiting for a return). Again, this might be nice for those who want to upgrade to Nuxt 3 who are using it.
Thanks @Gregg for your feedback.
We plan to have a Nuxt module to keep the current asyncData
behaviour when migrating from Nuxt 2 to Nuxt 3, you can see my current implementation (POC) here: https://github.com/nuxt/nuxt.js/blob/feat/async-data/examples/v3/async-data/modules/nuxt-async-data/plugins/async-data.js
I wanted to get closer to Vue core by keeping only one data
to:
I tried many ways to keep the same behaviour when navigating on client-side (ie: wait for all fetch calls before switching to the new route):
render
function of the component while fetch
is being calledenter
and leave
hook into the <transition>
component of <nuxt-child>
to force waitingBut these two were breaking other stuffs inside Vue internals (page transitions, keep-alive, etc) π’
Hi @Atinux , i working on quasar-framework and i see have a same thing in quasar framework.
https://quasar-framework.org/guide/app-prefetch-feature.html
so, how about it?
Hi @ttquoccuong
Actually, the prefetch
feature of Quasar is the same as the current fetch
of Nuxt.js: it's called during router.beforeEach
and you cannot have access to the component instance inside it.
First of all, I love this and it will definitely be a huge improvement for most of my projects. But there are some projects which are simpler and that kind of client-side UX might be too much overhead. Maybe the data being fetched is really small that there wouldn't be significant perceived speed improvement and it would cause an annoying flickering of the placeholder as the data is fetched really fast.
It's also a huge breaking change, as the whole Nuxt data fetching logic revolves mainly around asyncData
. The fetch
hook is a new way of thinking about data fetching and most users would only really benefit from it if they split their asyncData
into several fetch
hooks (just like the separation between post content and post author in the example).
The asyncData
module looks promising, but it gets in the way of the "avoid splitting component data into 2 hooks" principle (also, autocompletion would be nice). I want to use fetch
, but I don't want to rethink data fetching for now, at least.
So, my suggestion is to add a awaitFetch
property on page components. If set to true
, the fetch
hook of that page component would be awaited before changing routes, just like today's asyncData
. The fetch
hook on other components don't need to be awaited.
Defaulting awaitFetch
to true
, would allow a smooth migration from v2 to v3, since users can simply refactor their asyncData
into fetch
, keeping the current behaviour and incrementally adopting the new behaviour as they add fetch
on other components or set awaitFetch
to false
. Then, maybe, the awaitFetch
could default to false
on v4 to encourage the new behaviour.
That was how I wanted to implement it @henriqemalheiros, exactly like this to have no breaking changes and a smooth upgrade to all users...But sadly Vue.js does not have any asynchronous
hook before rendering the component data.
I tried to hack <router-view>
but without success, I am waiting for @posva expertise to see what we can do to support the current Nuxt behaviour by default while having the new fetch (accessing this
inside the hook).
I was trying to find a way to achieve this and, from what I've found, it seems that the only way this could be done tidily would be through asynchronous lifecycle hooks. In the past, Evan said that there's hasn't been enough substantial benefits that justified implementing this feature. Maybe, with Nuxt on the scene, they could implement it in Vue 3?
One possible alternative solution would be tackling this question (small repro here). vue-router
's inner workings were completely obscure to me until today and I haven't tried to code anything but, after some thought, it seems that if we could link the route instance registration (here and here) to beforeRouteEnter
's callback function pooling, we could defer the route update function (which seems to actually update the component being rendered by router-view
) and add the future awaited route as an another property (something like this.future
, available via $router.futureRoute
). But for this to work, router-view
render logic would need to be rethinked, since it would need to register the future route instance too. I think this could be achieved with two slots, one for the current route and other for the future route, and a conditional render between the two. Again, this is just plain speculation and if I can find some spare time, I'll try it.
Hello.
I've just seen issue this referenced in this issue I commented on...
One possible alternative solution would be tackling this question (small repro here)
...so perhaps I'll add my thoughts (though they may not be particularly relevant).
In brief, that issue relates to what might be loosely termed a "race condition" regarding fetched data and rendered component:
beforeRouteEnter
next()
is callednext(vm => ... )
The issue is that because the data has not been made available in mount, both template and computed properties require v-if
s, conditionals or empty data to prevent errors.
I'm not so familiar with nuxt (just a couple of practice projects) but it seems that this proposal does not look to solve that, as the component is rendered first?
It seems to me that some way to merge the data before mounting would be the key to cleaner templates and no nextTick()
funkiness or conditional cruft.
I made some suggestions in my comment on how this might be achieved, but as @henriqemalheiros noted, the code in Vue Router seems very complex / abstracted so it's something that would certainly need attention from @posva. I forked the repo and had a good dig about β to no avail!
@davestewart your solution is good, but it achieves the same UX as the current Nuxt version. The problem we're discussing is accessing the component's instance before the route changes. Currently, vue-router
does this:
beforeRouteEnter
beforeCreate
created
beforeMount
mounted
next()
callback defined on beforeRouteEnter
If we could move the next()
callback right after the created
hook, we would have access to the component instance and somehow defer its mounting until some async data is fetched. So I suggested using slots in router-view
or even (thinking about it later) something similar to the transition
component. I tried messing around with things a bit, but with no success. It kind of works in the simplest scenario, but it's very brittle and falls apart quickly. I think the $futureRoute
is the way to go, the problem being how to properly handle it in router-view
.
Yes, we're looking to solve the same problem.
My proposal looks to similar to asyncData()
as you mention:
beforeRouteEnter
data = getData() <-- get the user data
next(data) <-- I suggest passing `data` to next() here
<-- which will be merged by vue or vue router here
beforeCreate
<-- or maybe here
created
<-- you want to execute the callback here (which works for more functionality)
beforeMount
mounted
callback() <-- current place callback is executed
https://jsfiddle.net/tsyav1up/2/
You mention:
if we could move the
callback()
right after thecreated
hook, we would have access to the component instance and somehow defer its mounting until some async data is fetched"
The bits I don't understand:
beforeRouteEnter
?Perhaps this is Nuxt internals I don't understand...
And ignore this if it's hijacking the RFC.
@davestewart you're using getData()
as an external function that relies in the route params, like this.
That's exactly what we currently have in Nuxt and that is not what this RFC is about. This RFC is introducing a new way to handle data fetching that is closer to what major SPAs do, like Facebook or YouTube. It also supports access to this
, which is awesome in so many ways. It introduces a new DX and a new UX, the later being a breaking change.
As @Atinux said:
I tried to hack
<router-view>
but without success, I am waiting for @posva expertise to see what we can do to support the current Nuxt behaviour by default while having the new fetch (accessingthis
inside the hook).
So we want to use getData()
as an internal function that relies on the component instance, like this (data fetching depends on the path
prop). This way we could benefit from the new DX this RFC introduces without worrying about the breaking change in the UX.
This is the topmost feature I wait for in Nuxt 3 π
I need this feature :-)
New PR is up -> https://github.com/nuxt/nuxt.js/pull/6880
Maybe my opinion will change at some point, but for sites that are pretty quick I think showing users a loaded page is better than showing them a quasi-broken page where they have to wait longer and watch items load.
This is very similar to sites who lazy load images when they enter the viewport (very annoying) vs just BEFORE they enter the viewport.
I'm glad to see there is a way to do it the 'classic' way in v3 (though it also seems there is a fetch polyfill for some reason, it would be nice to NOT have that if we aren't using this new feature.)
@hecktarzuli I would disagree with this statement. One of the important parts of UX is loading speed and speaking from the personal experience, perceptive speed of loading process increases significantly when done in a new way so I would rather go the new way than the old one
@AndrewBogdanovTSS Good thing we have both options then eh :)
It's similar to pages like this. On a Desktop, go to https://realtruck.com/, click on a category, then go directly to another category (via top menu or whatever), then hit back/forward/back/forward. You see the content change, then load when you are already 'in' the page. It's probably a little picky, but it annoys me.
Yes we could put in the classic placeholder chunks everywhere, but it would actually be a worse experience. If we could just wait ~ 50-100ms and have everything perfect when you hit the page, that's where the sweet spot is.
I do understand having a loading state for slow mobile is also ideal, but looking at RUM, that's < 1% of our users.
Surely it's a no-brainer β implement the new behaviour and polyfill the old behaviour. It's got to be relatively trivial versus doing it the other way around. Some kind of observer/listener on $isFetching
in the beforeRouteEnter
hook?
If I had a magic wand we'd have 1 fetch method and a way to tell Nuxt to wait XXms for fetches to resolve before starting to render the route.
If all fetches resolve before the wait, great! Wait no longer and the page is rendered as intended. If the wait expires, nav to the route and the widgets would show placeholders until they are filled.
It's the font-display:fallback of routing - the best of all worlds :)
Well that was my wish too, but not possible since we have to create the instance of the page (so showing it) to call fetch π’
On SSR there is not placeholder since we can wait before rendering the HTML.
One good news, in full static mode, placeholders should be hidden since the data will be available for the rendered components π
Well that was my wish too, but not possible since we have to create the instance of the page (so showing it) to call fetch
Yep, hence the word 'magic'. π You'd pretty much have to re-think this whole feature, and maybe even have to hack Vue/Vue-Router. A guy can DREAM!
Already, gradually in many projects there is a migration to composition api. Is it supposed that the major version of Nuxt 3 will be for Vue 2?
Composition API is optional @negezor
We will support it as soon as it actually better support SSR :)
@hecktarzuli, you described the perfect end-user experience:
The only means I could find to accomplish this today was with the transition
option. I put the following in every page component:
<template>
<div>β¦</div>
</template>
<script>
export default {
transition: { name: "page", mode: "" },
// β¦
};
</script>
This makes use of the following global CSS:
.page-leave-active {
/* delay gives extra time for data fetching */
transition-delay: 0.4s;
transition-duration: 0;
}
This gets the job done, but I'd really like more programmatic control, such as:
@pi0 @Atinux
I have been playing with the new fetch for last few days and came across some practical hurdles, the most painful of them is error handling. The way it currently implemented it allows only hiding the component through $fetchState.error
variable. So if $fetchState.error===true
you can only hide your component. But often I want to be taken to error page if data is not found and return 404 , as I would normally do with asyncdata via error method.
<span v-if="$fetchState.pending" >loading</span>
<span v-else-if="$fetchState.error" >error</span>
<Quote :item="quote" />
.....
async fetch() {
try {
this.quote = await this.$api.getQuote(this.$route.params.id)
} catch (e) {
this.$nuxt.error({
statusCode: 404,
message: "Quote not found " + this.$route.params.id
})
this.$fetchState.error = true
// throw new Error("Aforizm tapΔ±lmadΔ±: " + this.$route.params.id)
}
},
So I try the above code and it works fine on client-side, but if error happens during SSR I get the following warning:
The client-side rendered virtual DOM tree is not matching server-rendered content
So how this most common error handling pattern should be implemented via new Fetch hook ? Thanks
Closing since it is available in Nuxt 2.12: https://nuxtjs.org/api/pages-fetch
Summary
Vue 2.6 introduced the serverPrefetch hook on SSR. Allowing to have an asynchronous hook for components to be awaited before rendering the HTML.
The idea is to introduce a new hook called
fetch()
that will allow any component to handle asynchronous operation on both server-side and client-side.Basic example
This is how a page component can look like:
pages/index.vue
You can see a more detailed example and documentation here: https://github.com/nuxt/nuxt.js/tree/feat/async-data/examples/v3/fetch
Motivation
The main motivation here is to remove the correlation between pages & asynchronous data. Each component could have its own async data logic.
This could also introduce a way for Nuxt modules author to create components to fetch data on particular endpoints.
Example:
Where
~/components/Post.vue
is something like:Detailed design
A schema is worth a thousand words π
Drawbacks
Context
fetch
hook does not receive anycontext
as 1st argument anymore since it has access tothis
.The context will be updated to be available through
this.$ctx
,this.$config
andthis.$nuxt
, learn more on https://github.com/nuxt/rfcs/issues/25Server-side
No drawbacks since Nuxt will wait for all
fetch
hooks to be finished before rendering the page.Client-side
The main drawback of this current implementation if the UX between Nuxt 2 & Nuxt 3 when navigating from page to page.
Let's take an example of having two pages (
A
andB
) withfetch
used in both of these pages.A -> middleware -> fetch/asyncData -> B
A -> middleware -> B -> fetch
This implies to create placeholders to display something while
fetch
is being called. This is why$isFetching
is introduced.We could support the "old" behaviour by providing a Nuxt module (using Nuxt middleware + plugin), a POC has been made on https://github.com/nuxt/nuxt.js/tree/feat/async-data/examples/v3/async-data
Advantages
People coming from Vue applications should find the new usage of
fetch
easier.this
, no need to learn some mystery Nuxt contextbeforeMount
ormounted
hook to call asynchronous data, renaming it tofetch
should just workfetch
is called duringbeforeMount
hookUnresolved questions
placeholder
property to overwriterender
and show this component until$isFetching
becomesfalse
?unexpected
error in thefetch
hook?Things left:
$progress
bar to handle multiplefetch
calls, see https://github.com/f/vue-wait