nuxt / nuxt

The Intuitive Vue Framework.
https://nuxt.com
MIT License
52.28k stars 4.78k forks source link

identify opportunities to improve INP on mobile devices #26271

Open LukasHechenbergerID opened 2 months ago

LukasHechenbergerID commented 2 months ago

Environment

Nuxt 3.10.4 Prerendered sites Hosted on Netlify

Reproduction

Most live Nuxt websites have the same problem

Describe the bug

(I've already posted in Github discussion the same problem https://github.com/nuxt/nuxt/discussions/25960, but now the new Web Vital is live, so i think it is quite severe)

We have a relatively new website built with Nuxt 3 and Storyblok as CMS. We prerender almost all sites and we have really good real life user scores in all Google pagespeed metrics. But the new metric INP (https://web.dev/articles/inp) is 233ms on mobile, which is not optimal (on desktop its around 100 so its okay).

image

I've also looked at some other nuxt sites and https://nuxt.com/ for example has an INP of 604ms on mobile! (https://pagespeed.web.dev/analysis/https-nuxt-com/5nu8bd42xc?form_factor=mobile) Other cases with similar scores as us: OpenAI: https://pagespeed.web.dev/analysis/https-openai-com/nqi508yurn?form_factor=mobile Ecosia: https://pagespeed.web.dev/analysis/https-www-ecosia-org/mk6jl9e1pc?form_factor=mobile Tiktok B2B: https://pagespeed.web.dev/analysis/https-www-tiktok-com-business-es/ol9rsuh9c8?form_factor=mobile

I've read that because of the high load/blocking time on the main thread, frameworks like Nuxt or React have a much higher INP than other websites (e.g. our old wordpress website is at 172ms right now, which is good enough for Google). This is especially true on weaker mobile devices. image I don't know, but maybe this time is especially large on weaker devices (or every step shown here is generally longer)?

I know that we have quite a lot of features and interactions on our website and the sites themselves are sometimes quite long, so that could also be the cause, but i wanted to ask if anyone gets similar results or knows how to reduce it on weaker devices.

Thanks!

Additional context

No response

Logs

No response

danielroe commented 2 months ago

Making performant websites is something that's really important to me.

You're right that https://nuxt.com has high INP, for various reasons. On the other hand, my own website has quite low INP. It's not Nuxt itself that is creating a high INP in this case.

INP also depends on various decisions taken by developers, including how to handle 'pending' states (for example, do you await your useAsyncData, or render a skeleton loading state instead - I would always recommend the latter). And how quickly you show feedback to the user based on user interaction. In general it's helpful to think about INP not about being whether your site is slow but about whether its visible 'reactions' seem slow. There are some very good guides on improving your INP, such as https://web.dev/articles/optimize-long-tasks.

There are some things that Nuxt itself can do too, of course, and I'm keen to do whatever we can to help. For example, we introduced an experimental feature which you can enable by setting experimental.defaults.useAsyncData.deep to false (example) to avoid deeply watching your payload data. The default is likely to change in our next major version. And any other suggestions are welcome.

Optimising your site probably needs to start with your own code, and along the way you may discover things that Nuxt can do too. Please let me know if so and I'll do my best to help. šŸ™

LukasHechenbergerID commented 1 month ago

Thanks for the answer Daniel! I was aware that it heavily depends on how the site itself is programmed, how big it is etc. but because most of the sites on the Nuxt showcase page also showed similar INP values I thought maybe there is more to it. (e.g. your page is of course well optimized, but you also don't use a Headless CMS in the background as far as I can tell)

I analyzed our page more thouroughly and one thing I found is quite strange to me. We currently rely heavily on prerendering because we have to fetch quite some data. In the beginning we did a lot of lazy loading with skeletons (as you mentioned), but the data isn't changing much, so now we are adding our Storyblok sites to nitroConfig.prerender.routes and all gets nicely prerendered when we publish a change (but there is still a server running because of hybrid rendering, in the future we will have some not prerendered sites).

But now to the problem: The first time you navigate to a site the web vitals plugin in Chrome reports good speed (like 24-40ms on my good laptop). But if you navigate to another site through the menu and back again then the interaction is much longer (like 300-500ms). It is really strange because there isn't even a network request or anything like that, so how can it take as long as the first time navigating?? We use the loading bar from Nuxt and I saw that on the first time navigating it instantly started to move (because i set the throttle to zero), hence the low INP, but on the second time navigating it takes longer to start, but finishes quite immediately. So I think that there is something big happening before even the hook page:loading:start is fired.

You can try it yourself: https://firmenwagen.insta-drive.com/at just click "Marken" in the menu and then to go back click on the logo on the left. (Apparently the effect is also depended on how much data a site has, e.g. on smaller sites the INP is "only" 200-300ms or so, but still much more than on first navigating)

We also have a preview site with no prerendering, just "normal" Nuxt and we await every useAsyncData and there we don't have this problem at all (https://firmenwagen-preview.insta-drive.com/at).

I also tried experimental.defaults.useAsyncData.deep, but no success. (I don't think it matters in my context)

I appreciate suggestions and help!

danielroe commented 1 month ago

@LukasHechenbergerID Can you reproduce this long delay when navigating on a minimal reproduction? šŸ™

LukasHechenbergerID commented 1 month ago

I've tried my best here: https://github.com/LukasHechenbergerID/nuxt-prerender-bug Live site is here: https://nuxt-prerender-bug.netlify.app/

As I've said I can only reproduce it with hybrid rendering -> prerendering hence the live site (is there a way to test this locally?). The last page in the menu is not prerendered and doesn't change interaction time when going back and forth, so probably this is a problem with prerendering.

It's only a problem if larger amounts of data is fetched. (but the interaction time from the page with less data doubles also but is only like 40ms on my machine).

If you switch from these two back and forth: image you can see the much higher interaction time (in the console with the Web Vitals Chrome plugin) when switching to a page for the second time.

danielroe commented 1 month ago

Thank you! I'll check that out... This sounds like something we can likely fix and linked to prerendering/payload extraction.

danielroe commented 1 month ago

@LukasHechenbergerID Taking a look, it seems what is taking the time is rendering the big blob of JSON in the page. When I comment that out, I can't reproduce any issues with INP in that project. The behaviour doesn't seem to vary based on whether the page is prerendered or not.

Can you reproduce without rendering the blob of data in the page?

(You can test prerendered pages locally with pnpm build && pnpm preview.)

Am I missing something?

LukasHechenbergerID commented 1 month ago

@danielroe You're right, the problem only occurs when data from Vue is rendered.

But there is still a difference between prerendered and not I think (but probably it comes down to the same problem as I explain below): image (All statements are for the second visit to a route, so you need to click on a page link, click on another and then again on the previous. On the first load or click on page link there is no problem with any page (That is quite strange to me still))

Because the static payload routes are having the same trouble regardless of prerendering or not I think it comes down to Vue rendering. Maybe the prerendered route with big data is the slowest because the data is not fetched but loading from the nuxt payload as as variable? And the extraction from the nuxt payload + rendering is the cause of the highest INP?

As you see the same exact site (with big data to fetch and render) but not prerendered has no problem at all & on the first visit to a route there is also no problem... But to the contrary I even see that the prerendered route is loading faster than the not preloaded route, so maybe Chrome is recognizing that there is something going on on the not prerendered route (because of the immediate data fetching? or the loading indicator?) but thinks the site is a bit stuck when going to a prerendered route.

(I have pushed the new routes to the repository, if you want to check it)

LukasHechenbergerID commented 1 day ago

@danielroe Do you have any updates on this? Or a lead/idea I could follow? As I explained above, there is a difference between prerendered and not, but only when I want to render a big payload...

I use Storyblok as a CMS, so when i switch pages there is naturally much data to render (the prerendered html is only used on the first page).