Enhance the Vue.js performance by preloading components

After discovering the benefits of lazy loading components, I decided to start implementing it in my project. However, I encountered some issues when trying to prefetch the lazy loaded components and vue-router routes. Upon inspecting with Chrome DevTools, I noticed that the lazy loaded chunks are only loaded when we navigate to the specific route or when a v-if statement evaluates to true, rendering the component.

I attempted to utilize the webpackPrefetch: true string in both the router configuration and component import statement, but unfortunately, it did not have any noticeable impact.

Project structure:
Master-Detail layout

router config:

import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);

var routes = [  
  {
    path: "/DetailPage",
    component: () => import(/* webpackChunkName: "Detail-chunk" */ "AppModules/views/MyModuleName/DetailPage.vue")
  },
  {
    path: "/MasterPage",
    component: () => import("AppModules/views/MyModuleName/MasterPage.vue")
  }
]

export const router = new Router({
  routes: routes,
  stringifyQuery(query) {
    // encrypt query string here
  }
});

export default router;

Master view:

<template> 
  <div @click="navigate">
    Some text
  </div>
</template>

<script>
export default {
  name: "MasterPage",
  methods: {
    navigate() {
      this.$router.push({
        path: "/DetailPage",
        query: {},
      });
    },
  },
};
</script>

Details page:

<template>
  <div>    
    <my-component v-if="showComponent" />
    <div @click="showComponent = true">Show Component</div>
  </div>
</template>

<script>
const MyComponent = () => import(/* webpackChunkName: "MyComponent-chunk" */ "AppCore/components/AppElements/Helpers/MyComponent");
export default {
  name: "DetailPage",
  components: {
    MyComponent,
  },
  data() {
    return {
      showComponent: false
    }
  }
};
</script>

vue.js.config file:

const path = require("path");

const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;

module.exports = {
  publicPath: "some-url",
  outputDir: "./some/path",
  chainWebpack: webapckConfig => {
    webapckConfig.plugin("html").tap(() => {
      return [
        {
          inject: true,
          filename: "index.html",
          template: "./public/index.html"
        }
      ];
    });
  },
  productionSourceMap: true,
  configureWebpack: {
    plugins: [
      new BundleAnalyzerPlugin({
        analyzerMode: "server",
        generateStatsFile: false,
        statsOptions: {
          excludeModules: "node_modules"
        }
      })
    ],
    output: {
      filename: "some file name",
      libraryTarget: "window"
    },
    module: {
      rules: [
        {
          test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
          use: [
            {
              loader: "url-loader",
              options: {
                limit: 50000,
                fallback: "file-loader",
                outputPath: "/assets/fonts",
                name: "[name].[ext]?hash=[hash]"
              }
            }
          ]
        }
      ]
    },
    resolve: {
      alias: {
        vue$: process.env.NODE_ENV == 'production' ? 'vue/dist/vue.min.js' : 'vue/dist/vue.js',
        AppCore: path.resolve(__dirname, "..", "..", "AppCoreLite"),
        AppModules: path.resolve(__dirname, "..", "..", "AppModulesLite")
      }
    }
  }
};

Despite splitting async routes and components into separate chunks, these chunks are not prefetched. When navigating to the master view, Detail-chunk.[hash].js is not seen in the network tab until the navigation method is triggered. Similarly, on the details page, MyComponent-chunk.[hash].js is only requested when showComponent becomes true upon clicking a button.

It's worth noting that there are claims stating that vue-cli v3 has prefetch functionality enabled by default and does not require the webpack magic string. Removing the webpackPrefetch comment did not yield any changes in behavior.

When running vue-cli-service inspect, I confirmed that the prefetch plugin is indeed present in the webpack config:

 /* config.plugin('preload') */
    new PreloadPlugin(
      {
        rel: 'preload',
        include: 'initial',
        fileBlacklist: [
          /\.map$/,
          /hot-update\.js$/
        ]
      }
    ),
    /* config.plugin('prefetch') */
    new PreloadPlugin(
      {
        rel: 'prefetch',
        include: 'asyncChunks'
      }
    ),

UPDATE: Despite attempting to remove the prefetch webpack plugin and utilizing the webpack magic comment, no difference was observed.

How can I successfully implement prefetch functionality in my project?

Answer №1

My solution involved the development of a straightforward prefetch component that initiates after a specified duration.

PrefetchComponent.vue

<script>
import LazyComp1 from "./LazyComp1.vue";
import LazyComp2 from "./LazyComp2.vue";
export default {
    components:{
        LazyComp1,
        LazyComp2,
    }
}
</script>

MainApp.vue

<template>
    <PrefechLoader v-if="loadPrefetch"></PrefechLoader>
</template>
<script>
export default {
    components: {
        PrefechLoader: () => import("./PrefetchComponent");
    },
    data() {
        return {
            loadPrefetch: false
        }
    },
    mounted() {
        setTimeout(() => {
            this.loadPrefetch = true;
        }, 1000);
    }
}
</script>

Answer №2

due to issues with vue-router-prefetch, I manually implemented route prefetching.

Vue 3 Sample - loading async components for all routes on page load

const router = createRouter({
    history: createWebHistory(),
    routes: [{
        path: '/',
        component: HomeView
    }, {
        path: '/about',
        component: () => import('./views/AboutView.vue')
    }]
});

async function preloadAsyncRoutes() {
    // iterate through all routes and prefetch async components if any
    for (const route of router.getRoutes()) {
        if (!route.components) continue;

        // loop through components in case named views are used
        for (const componentOrImporter of Object.values(route.components)) {
            if (typeof componentOrImporter === 'function') {
                try {
                    // prefetch the component and wait for it to finish before moving to the next one
                    await componentOrImporter();
                } catch (err) {
                    // handle failed requests gracefully
                }
            }
        }
    }
}

window.addEventListener('load', preloadAsyncRoutes);

Answer №3

Lazy loaded elements are designed to be activated only upon the user's interaction with the route. If you desire to load an element prior to this, simply refrain from employing lazy loading.

vue-router will preload components into memory and switch out the tag's content dynamically, even when utilizing non-lazy loaded components.

Answer №4

To fulfill your requirements, consider incorporating the vue-router-prefetch package. A functional demonstration is available for reference.

Important: The demo showcases that only page 2 is prefetched by the QuickLink component, which has been imported from the vue-router-prefetch.

Code:

import Vue from "vue";
import Router from "vue-router";
import RoutePrefetch from "vue-router-prefetch";

Vue.use(Router);

Vue.use(RoutePrefetch, {
  componentName: "QuickLink"
});

const SiteNav = {
  template: `<div>
    <ul>
      <li>
        <router-link to="/page/1">page 1</router-link>
      </li>
      <li>
        <quick-link to="/page/2">page 2</quick-link>
      </li>
      <li>
        <router-link to="/page/3">page 3</router-link>
      </li>
    </ul>
  </div>`
};
const createPage = (id) => async() => {
  console.log(`fetching page ${id}`);
  return {
    template: `<div>
          <h1>page {id}</h1>
          <SiteNav />
        </div>`,
    components: {
      SiteNav
    }
  };
};

const routers = new Router({
  mode: "history",
  routes: [{
    path: "/",
    component: {
      template: `<div>
          <h1>hi</h1>
          <SiteNav />
        </div>`,
      components: {
        SiteNav
      }
    }
  }]
});

for (let i = 1; i <= 3; i++) {
  routers.addRoutes([{
    path: `/page/${i + 1}`,
    component: createPage(i + 1)
  }]);
}

export default routers;

Answer №5

I am currently developing a mobile application and I need to dynamically load some components while displaying the splash screen.

Although @Thomas's suggestion of using a Prefetch component is helpful, it does not load the component in the shadow DOM and does not pass Vetur validation (as each component must have its template).

Below is the code I have implemented:

Main.vue

<template>
    <loader />
</template>
<script>
    import Loader from './Loader'
    const Prefetch = () => import('./Prefetch')
    export default {
        name: 'Main',
        components: {
            Loader,
            Prefetch
        }
    }
</script>

Prefetch.vue

<template>
    <div id="prefetch">
        <lazy-comp-a />
        <lazy-comp-b />
    </div>
</template>
<script>
import Vue from 'vue'

import LazyCompA from './LazyCompA'
import LazyCompB from './LazyCompB'

Vue.use(LazyCompA)
Vue.use(LazyCompB)
export default {
    components: {
        LazyCompA,
        LazyCompB
    }
}
</script>

<style lang="scss" scoped>
#prefetch {
    display: none !important;
}
</style>

The loader component is loaded and rendered first, allowing the Prefetch component to dynamically load content afterwards.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering an undefined error while attempting to retrieve an object from an array by index in Angular

Once the page is loaded, it retrieves data on countries from my rest api. When a user selects a country, it then loads the corresponding cities for that country. Everything is functioning properly up to this point, however, upon opening the page, the city ...

Disable body scrolling on mobile devices in native browsers

When using the native browser on Samsung Galaxy S5 / S6, the following CSS code: body { overflow: hidden; } does not successfully prevent the body from scrolling. Is there a way to work around this issue? Edit: As mentioned below, one workaround is t ...

The functionality of angular-ui's ui-utils and ui-scroll module is currently nonfunctional in version 0.1.0

I have been trying to implement the features from this Angular UI library: http://angular-ui.github.io/ui-utils/, particularly focusing on this aspect: https://github.com/angular-ui/ui-utils/blob/master/modules/scroll/README.md Unfortunately, despite my e ...

What is the reason behind Angular not allowing users to define @Output events that begin with 'on'?

While developing a component, I defined an output EventEmitter named onUploaded. However, Angular flagged an error instructing me to use (uploaded) instead. This restriction is due to security concerns, as bindings starting with 'ono' pose risks. ...

AngularJS factory with local storage functionality

As a newcomer to IonicFrameWork, I decided to try out their "starter tab" template and made some tweaks to the functionality of deleting and bookmarking items from a factory. In my books.js file where the factory is defined, here's a snippet of what ...

Are there any jQuery Context Menu plugins clever enough to handle window borders seamlessly?

After reviewing UIkit, as well as some other jQuery Context Menu plugins, I have noticed that they all tend to exhibit a similar behavior: The actual menu div renders outside the window, causing valuable content to be hidden from view. Is there a way to ...

Displaying Spinner and Component Simultaneously in React when State Changes with useEffect

Incorporating conditional rendering to display the spinner during the initial API call is crucial. Additionally, when the date changes in the dependency array of the useEffect, it triggers another API call. Question: If the data retrieval process is inco ...

Is Passport.js' serializeUser and deserializeUser functions never triggering?

Encountering an issue with Passport-local. It seems that neither serializeuser nor deserializeUser are being invoked. Upon researching similar problems on SO, it appears that many others facing this problem were not properly including bodyParser. Below is ...

RTM is lacking dropdown navigation menus

In Visual Studio 2013 beta versions, there were dropdown menus at the top of the javascript editor that functioned similarly to those in c# and vb editing. Have these been removed from the RTM or final release, or are they available with a specific version ...

When the button is clicked, bind the value to an object using the specified key in

I'm fairly new to Vue and I'm looking to generate buttons based on my data and show their information when clicked. The 'pets' object contains keys for id and info. (My actual data is more extensive and my code is a bit more complex, bu ...

What steps can I take to stop a browser from triggering a ContextMenu event when a user performs a prolonged touch

While touch events are supported by browsers and may trigger mouse events, in certain industrial settings, it is preferred to handle all touch events as click events. Is there a way to globally disable context menu events generated by the browser? Alternat ...

Execute the gulp module on the source files

Recently, I've been delving into the world of gulp and trying to enhance the readability of my js source files. I have a task in place (which executes successfully) that utilizes 'gulp-beautify' to beautify the js files: gulp.task('js& ...

Tap on the HTML5 video to exit the fullscreen mode

Objective I have successfully implemented a fullscreen video setup that triggers when a link is tapped on a mobile device. To maintain a clean aesthetic, I have hidden the HTML5 video controls using CSS. The desired functionality includes closing the full ...

Dealing with shared content in your Capacitor Android application may require some specific steps

As a beginner in android development, I am currently embarking on my first Capacitor Android app project using Quasar/Vue. My goal is to enable the app to receive files/images shared from other apps. Through research, I have discovered how to register my a ...

Why is it that GetElements does not provide immediate results upon execution?

Just diving into the world of Javascript for the first time and experimenting with it on Chrome, but running into unexpected results. When I try: document.getElementsByTagName("h1") I anticipate seeing: <h1>tester h1 in body</h1> Instead, wh ...

Guide on implementing casl-vue in nuxt without needing to refresh the page

While experimenting with authorization in Nuxt using casl-vue, I encountered an issue that only resolves after a page refresh. I created my own plugin file: plugins: ['@/plugins/roles'], Here is the content of my roles file: import Vue from &ap ...

Utilize the @blur event within flatpickr to trigger actions when the output is empty

<b-col md="7" offset-md="1"> <b-form-group> <template> Date and Time <flat-pickr id="datetime" v-model="datetime" class="form-control" ...

Response coming from an ajax call in the form of a JSON

With the JSON string provided below: {cols:[{"id":"t","label":"Title","type":"string"},{"id":"l","label":"Avg ","type":"string"},{"id":"lb","label":"High","type":"string"},{"id":"lo","label":"Low","type":"string"}],rows:[{"c":[{"v":"Change navigation"},{"v ...

Encountering a MiniCssExtractPlugin error while trying to build with npm

I have encountered an issue while trying to execute "Npm Run Build" on my reactjs website. The error message I keep receiving is as follows: /usr/local/lib/node_modules/react-scripts/config/webpack.config.js:664 new MiniCssExtractPlugin({ ^ TypeErr ...

How can I transfer a MongoDB collection to an EJS file in the form of a sorted list?

I have successfully displayed the collection as a json object in its own route, but now I want to show the collection in a list under my index.ejs file (I'm still new to ejs and MongoDB). Below is the code that allows me to view the json object at lo ...