Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimisations for prefetching chunks #14584

Open
4 of 7 tasks
danielroe opened this issue Aug 15, 2022 · 23 comments
Open
4 of 7 tasks

optimisations for prefetching chunks #14584

danielroe opened this issue Aug 15, 2022 · 23 comments

Comments

@danielroe
Copy link
Member

danielroe commented Aug 15, 2022

Currently dynamic imports include things like routes, error components, and any global components (so if using with @nuxt/content). I am very concerned to decrease HTML and number of prefetched resources but if we simply disable prefetching, sites will seem much slower.

From my point of view, before removing current prefetch behaviour, we need the ability to make some finer-grained decisions.

A set of suggestions:

@kosmeln
Copy link

kosmeln commented Aug 15, 2022

Hello @danielroe .
I'm not sure if this is related to this improvement or I should create a separate issue. Please let me know.
I've created a test project to visualise how pre-fetching stylesheets may affect the way page looks.
There are 2 layouts - red and blue (under /layouts). If I open red-page component (https://8xvt77.sse.codesandbox.io/red-page), blue layout - that is being pre-fetched - affects the way page looks - and h2 that should be black displays in blue instead.
The example is oversimplified but we have a real use case of this being a problem for our project.

@pi0
Copy link
Member

pi0 commented Aug 15, 2022

@kosmeln Please ensure to use scoped styles (<style scoped>) or selector prefixes per parent/layout. You can use a special CSS class in layouts that enable them otherwise, loading a layout can make unpredictable behavior. See this fork: https://codesandbox.io/s/nuxt3-playground-rc-8-forked-6l6uot?file=/layouts/blue.vue:122-134

@pi0
Copy link
Member

pi0 commented Aug 15, 2022

@danielroe As we discussed, we can do both of first items in parallel.

Build-time and manifest based page prefetching is probably something we don't want to do in Nuxt 3 since was always tricky in Nuxt 2 when number of pages increases. Only reliable way to predict next pages is runtime rendering. Both Server-Side and Client-Side of NuxtLink can smartly add prefetches. (for now, I've grayed out 3 last items. we can revise once first improvements added if we really need to have build-time manifest tweaks)

@kosmeln
Copy link

kosmeln commented Aug 15, 2022

@kosmeln Please ensure to use scoped styles (<style scoped>) or selector prefixes per parent/layout. You can use a special CSS class in layouts that enable them otherwise, loading a layout can make unpredictable behavior. See this fork: https://codesandbox.io/s/nuxt3-playground-rc-8-forked-6l6uot?file=/layouts/blue.vue:122-134

Thanks for the quick response @pi0.

I do aware of this solution and this is what I currently use as a workaround for my project.
The thing is - in my current project we use single NUXT app for multiple sites. We have 1 layout per 1 site.
Each site uses same vue components as the underlaying logic is mostly the same. We use the layout split purely for css tweaks.
We use global styles that only contains the css difference for a current site in addition to loading same common styles for all sites. So the global styles are used as an override for some common css rules.
FYI: When we used NUXT2 this worked perfectly with setting

splitChunks: {
    layouts: true
}

Speaking of other use cases, there is an example posted by @engvie of using similar approach when splitting between site and admin pages - see this thread.

The workaround that we use (same as you shared) resolves the issue, but the problem is - page pre-fetches all layouts (for my case a dozen of them) and they will never be needed in per-site approach - so this is just the waste of resources.

I really hope that there is going to be any mechanisms developed to handle this use-case either in the scope of this ticket or further on. Until then - any other suggestions that you might have are very welcomed.

@pi0
Copy link
Member

pi0 commented Aug 15, 2022

Thanks for explaining your use case @kosmeln. Prefetching is not causing any issue but simply reveals a side-effect issue of not using scoped styles in layouts. Using style prefixes is not a workaround but a proper way to write global styles that ensure they don't conflict.

I would be more than happy to continue this conversation to investigate different solutions we might provide for this. If you would, please open a discussion and elaborate more about site builder use-case that ends up with dozens of layouts.

BTW in Nuxt 3, we have a much better solution for theming and shared code for multi sites depending on same code base but slight changes. It is called extends.

@kosmeln
Copy link

kosmeln commented Aug 15, 2022

Thank you @pi0 !
I really appreciate the rapid turnaroud. I'll dig deeper into "extends" topic you shared and will open a discussion further on if needed as you suggested.

@jshimkoski
Copy link

jshimkoski commented Dec 2, 2022

I believe this to be related: #16231

As you can see in that discussion, simply removing prefetch tags from my app increases its Lighthouse score by 20%. And I can tell you the app feels and loads faster as well in use.

If developers could gain access to shouldPrefetch and shouldPreload, that would really help in ginormous applications like mine where prefetch actually leads to performance loss instead of gain.

@oleghalin
Copy link

Maybe it makes sense to include different prefetch strategies for NuxtLink? prefetch in viewport and on NuxtLink hover?

@sh0ber
Copy link

sh0ber commented Feb 14, 2023

Despite a manual workaround in which I disable prefetch/preload by modifying vue-bundle-renderer's runtime.mjs, Nuxt seems to be building an unnecessary critical request chain which also damages my Lighthouse scores.

I see there are chained requests for numerous pages and resources that are not even in view (I've tried no-prefetch and lazy component loading with no help.) Most of these are "modulepreloads" I don't want, and I can't remove them manually because they seem to be generated at runtime.

I want the option not to prefetch, preload, or modulepreload anything at all. It would be great to be able to:

  1. Disable all three of: prefetch, preload, modulepreload
  2. Inline these modules if desired. I'm guessing that this would also eliminate the critical request chain complaint from Lighthouse. It would still be loading unnecessary resources but would at least eliminate the unnecessary chaining of requests.

@danielroe
Copy link
Member Author

@sh0ber If vue-bundle-renderer isn't injecting the prefetches, then it will be Vite that is doing so. If you can provide a reproduction we can identify the cause and either work around it within Nuxt or resolve within Vite.

@edwh
Copy link
Contributor

edwh commented Mar 28, 2023

I'd just like to question the assumption that disabling prefetching will make sites seem slower. If you have a site with a small number of user flows where the resources will end up getting fetched anyway, then that's true.

If you have a site with a large number of user flows where any given user will only do a few of them, then the prefetching will be pulling down a lot of assets that are never used. Even though prefetching happens in the background, that will still chew up CPU on the device, possibly network data for mobile. It can also become significant with data egress allowances/charges for the server side.

I think having a very clear "bundle and prefetch everything" or "prefetch nothing and fetch individually as you go" option might be a higher priority than finely granular control. I have this in my nuxt.config.ts:

  render: {
    bundleRenderer: {
      shouldPrefetch: () => false,
      shouldPreload: () => false,
    },
  },

...but I still see assets fetched which I wouldn't expect on v3.3.2. Is that the correct way of trying to turn off prefetching? Apologies if I've missed something - I've been wandering around in related issues.

Is there any way to debug what causes a specific component to get pulled in?

@bangjelkoski
Copy link

Hey, @danielroe are you guys planning to finish and release this in any of the upcoming Nuxt versions?

Copy link
Member Author

We are indeed 😊

@modbender
Copy link

modbender commented Oct 17, 2023

Really need an option to disable modulepreload @danielroe

I've tried

  vite: {
    build: {
      modulePreload: false,
    },
  },
  vite: {
    build: {
      modulePreload: {
        polyfill: false,
      },
    },
  },
  vite: {
    build: {
      modulePreload: {
        resolveDependencies: () => [],
      },
    },
  },

None of them work even though it seems like it's a part of Vite since version 3.1
https://vitejs.dev/config/build-options.html#build-modulepreload

As a matter of fact it's not even listed in vite.build Nuxt configurations
https://nuxt.com/docs/api/configuration/nuxt-config#build-1

@danielroe
Copy link
Member Author

@modbender would you explain a bit more about what, specifically, you want to achieve, in a new issue? 🙏

@modbender
Copy link

modbender commented Oct 18, 2023

@modbender would you explain a bit more about what, specifically, you want to achieve, in a new issue? 🙏

As much as I would love to, I do not have time to create reproduction, and my repository is private for commercial purposes.

All I know is, I created 2 sites, both static, deployed on Netlify. One uses only MD files with nuxt/content, while the other uses Strapi with nuxt/content's MDC element for rendering string to markdown.

Now getting to homepage performance on PageSpeed, I get different performance values for both
http://nuxt-nova.netlify.app/
image

https://nuxt-nova-md.netlify.app/
image

The problem here is FCP, paint starts late due to all the preloads, and increase LCP too.

There is one composable I created useStrapiOgImage which is the largest 660kb part of the chunks that are preloaded using modulepreload on homepage. But I am not using this composable anywhere else but the post page. So why preload it on home page?

There's just too many things going through modulepreload.

And also the same large composable gives this in pagespeed:

image

@or2e
Copy link

or2e commented Nov 2, 2023

@danielroe
Hey. In our case, we have this scenario

// app.vue

<LazyAuthBar
    v-if='!isPublicNetwork',
    :user='user'
/>
prefetch

image
image

That is, while on the public network, we still load the AuthBar component
It would be great to be able to control the vite/webpack magic comments from within the application code, but no idea what it should look like yet, maybe:

  • LazyPrefetchPreloadAuthBar
  • LazyPrefetchAuthBar
  • LazyPreloadAuthBar
    or
  • LazyNopreloadNoprefetchAuthBar
  • LazyNopreloadAuthBar
  • LazyNoprefetchAuthBar

press F for components.d.ts ;)


A temporary solution for us for now is to use build:manifest, but it has a drawback:

  • hardcoding
...
if (key === 'components/AuthBar.vue') {
  file.prefetch = false;
  file.preload = false;
}
...
  • on a private network this prefetch is necessary (not critical)

If we don't use Lazy, the AuthBar component will be included in app.vue (entry.js)


UPD

More critical is the scenario with the use of a dynamic layout:

...
<NuxtLayout :name="isMobile ? 'mobile' : 'desktop'">
  <NuxtPage />
</NuxtLayout>
...

Here's the sandbox, try building it and preview it

You can see that the mobile layout is prefetched

image


UPD2
@danielroe

As a result, in order not to load components for the desktop version in the mobile version and vice versa, we had to use build:manifest to disable prefetch for all Lazy* components, keep only *.global.vue, where prefetch would really be useful.

So I propose to change the default behavior, for Lazy* components to set prefetch: false

@nathanchase
Copy link
Contributor

nathanchase commented Dec 2, 2023

I'm in the same boat. I really don't want to have to modulepreload all of the components that a logged in user would need when it really should just be a splash page.

<template>
  <Homepage v-if="isLoggedIn" />
  <HomepageSplashMobile v-if="!isLoggedIn && device.isMobile" />
  <HomepageSplash v-if="!isLoggedIn && !device.isMobile" />
</template>
It's currently modulepreloading 45(!) components' worth of .js chunks, when it should really just be the entry chunk. image

@ViorelMocanu
Copy link

It's currently modulepreloading 45(!) components' worth of .js chunks, when it should really just be the entry chunk.
image

Same for https://pentest-tools.com – screenshots for modulepreload (here too – they're 62 in total). Ideally their number would be around 5-10 max, considering the components used in the homepage specifically.

@Tristan971
Copy link

Tristan971 commented Dec 3, 2023

While it's really not the fix we wanted, for now we got things slightly improved by:

  1. Disabling prefetch of a few things that really shouldn't be prefetched (like assets and dynamic imports)
  2. Forcing chunking on a per-page-folder basis (ie /pages/foo/* all go into a single chunk, letting rollup deal with placement of individual components inside those afterwards)
// nuxt.config.ts
...
  hooks: {
    // prevent some prefetch behaviour
    "build:manifest": (manifest) => {
      for (const key in manifest) {
        manifest[key].dynamicImports = [];

        const file = manifest[key];
        if (file.assets) {
          file.assets = file.assets.filter(
            (assetName) => !/.+\.(gif|jpe?g|png|svg)$/.test(assetName)
          );
        }
      }
    }
  },
...
  vite: {
    build: {
      rollupOptions: {
        output: {
          // target ~250KB per chunk in an ideal world
          experimentalMinChunkSize: 250 * 1024,
          manualChunks: (id, _) => {
            // need to avoid touching non-entrypoint files, otherwise it breaks bundling
            // because imports aren't idempotent
            if (
              !id.includes("node_modules") &&
              !id.startsWith("virtual:") &&
              !id.includes("src") &&
              !id.includes("assets")
            ) {
              // merge pages/foo/* as chunk-pg-foo, pages/bar/* as chunk-pg-bar, etc.
              // then merge pages/* (ie no subfolder) into chunk-pg-misc
              if (id.includes("pages")) {
                const parts = id.split("/");
                const folderIndex = parts.indexOf("pages");
                if (folderIndex + 2 < parts.length) {
                  const pageGroup = parts[folderIndex + 1];
                  return `chunk-pg-${pageGroup}`;
                }
                return "chunk-pg-misc";
              }
            }
          },
        },
      },
    },

While this doesn't solve unnecessary preloading entirely, we went from 200+ chunks (so when accounting for js+css, nearly 500 requests!) to about 30, and avoided hundreds of asset images getting preloaded.

I want to stress that outside of wasting time staring at lighthouse scores (they matter, but people do indeed tend to obsess too much about the difference between 90+ and 95+ for example):

  • every extra request is an extra chance for something to fail, so you really do not want hundreds of requests
  • every extra request is an increased resource strain on one's infrastructure (in our case, that amounted to thousands of extra requests per second despite aggressive client-side caching due to the size of the userbase, which is not free)

It's definitely worth testing a few ways to group pages/components in chunks and running builds to see the outcome. The optimal way varies a lot by website, so don't just copypaste our setup (in our case merging all components together somehow resulted in a much bigger bundle for example, and per-component-folder broke the site, etc. So do test it.)

@bbhxwl
Copy link

bbhxwl commented Feb 11, 2024

How to successfully disable prefetch?

@cwe-spb
Copy link

cwe-spb commented Feb 28, 2024

Are there any plans to configure the config to forcibly disable the prefetch?
@danielroe

@alimozdemir
Copy link

Hi @danielroe it looks like the list in the first post is not up-to-date, all PRs are merged. Could we have a quick summary of the status? Meanwhile, I would like to contribute to solving the issue related to "prefetch" and optimization, could you lead the way or are we stuck somehow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests