As of
Est. 

Asynchronous Components

Typically, Vue components are loaded synchronously with their parents. Read, how this can be decoupled.

Performance is typically not a topic with Vue and VitePress. The generated web pages will typically load and render very fast.

Problem

But there are cases where carelessness might hit your performance. I had a case like this when I naively included the TopicMap component in my customized VitePress Layout.

The map itself is an overlay that sits off-screen. When the toggle is activated, the overlay slides in. The overlay shows a map of all jottings and this is computed using the Cytoscape.js library.

Like always, I use Vue components in the template as appropriate and import them with static imports in the script element.

vue
// TopicMapToggle.vue
<template>
 <TopicMap/>
</template>

<script setup lang="ts">
 import TopicMap from "./components/TopicMap.vue";
</script>
// TopicMapToggle.vue
<template>
 <TopicMap/>
</template>

<script setup lang="ts">
 import TopicMap from "./components/TopicMap.vue";
</script>

Because the component is loaded as part of the main layout, Vue instantiates the template with all included components at page load. In my case that led to a situation where the whole time for the graph layout added to the load time of the page. In the end, the complete page load took several seconds.

Solution

Two observations help to optimize page load times:

  • Even though I used CSS to hide the map, it is still rendered off-screen and of course the initialization takes time. It would be enough to instantiate the component on demand.
  • The import happens when the parent component is instantiated. Components that run or load much code on import will also add to the page load time.
vue
// TopicMapToggle.vue
<template>
 <TopicMap v-if="show"/>
</template>

<script setup lang="ts">
 import { defineAsyncComponent } from 'vue'
 const TopicMap = defineAsyncComponent(() =>
  import('./components/TopicMap.vue')
 )
</script>
// TopicMapToggle.vue
<template>
 <TopicMap v-if="show"/>
</template>

<script setup lang="ts">
 import { defineAsyncComponent } from 'vue'
 const TopicMap = defineAsyncComponent(() =>
  import('./components/TopicMap.vue')
 )
</script>

Conditional Rendering

The first part is to decouple the components rendering from the parent component and thereby from the initial page load. With v-if the TopicMap is not rendered at all until show.value is flipped to true. That only happens when the TopicMapToggle is activated for the first time.

Asynchronous Components

The second part is about importing the component. With the static import, it is requested together with its parent component. To break this dependency, Vue offers the defineAsyncComponent function. This function creates a wrapper component which will load our component only when it really should be rendered. Combined with the conditional rendering, the dynamic import is postponed until the user activates the TopicMapToggle for the first time.

Further Improvement

Additionally, we could also switch show.value to true using setTimeout in the parent component. That would decouple the Topic Map from the page load but still give the opportunity to pre-render the graph before the user want's to see it.

Advanced topics

When the VitePress site is rebuild before the deferred loading actually happens, the JavaScript file might be replaced by another version. As a new version will get a different file name, the original version of the file will not be found, and the asynchronous component will fail. Defining app.config.errorHandler provides a simple solution for this problem.