Explore how to optimize your Nuxt app’s performance by controlling when components load. Learn about lazy components, prefetching, and making components asynchronous to enhance user experience and reduce initial load times.
Get notified when we release new tutorials, lessons, and other expert Nuxt content.
We have a few different ways we can control when a component is loaded in our Nuxt apps.
This is really useful, because we don’t always want everything loaded on the initial page load — that can make things slow if we’ve got a lot going on in a single page.
Let’s take a look at three different aspects of Nuxt that give us more control.
The first is by creating lazy components that are loaded asynchronously. The simplest way to do this is by adding the Lazy*
prefix when using a component:
<template>
<!-- Only fetch the Modal component when this is `true` -->
<LazyModal v-if="showModal" />
</template>
This puts the Modal
component in it’s own chunk, so it isn’t loaded on the initial page load. It will only be loaded when it needs to be rendered, when the v-if
evaluates to true
.
This can speed up our initial page load by a lot!
But there’s a problem here.
The benefit of using lazy components like this is the biggest when the component we’re lazy loading is huge. By moving a huge component that we don’t always need (or need immediately) out of our bundle we save a lot!
But when we do need it, it takes longer to fetch it.
Let’s say we have a modal component, and we only want to load it if the user is actually going to open it:
<template>
<!-- Clicking this will fetch the Modal component -->
<button @click="showModal = true">
Open Modal
</button>
<LazyModal v-if="showModal" />
</template>
<script setup>
const showModal = ref(false);
</script>
If the Modal
component is big enough, there will be a delay between clicking it and fetching it and then rendering it. And that makes for a poor UX.
That brings us to our second feature.
Luckily, Nuxt gives us a way to have finer control over when these lazy components get loaded. It’s called prefetchComponents, and we can use it to manually fetch components when we’re pretty sure they’ll be needed.
Using this method, we can wait to load the Modal
so we don’t slow down our initial page load, but we can do it before we need to render the component so the user doesn’t notice any delay:
<template>
<button
@click="showModal = true"
@mouseover="loadModal"
>
Open Modal
</button>
<LazyModal v-if="showModal" />
</template>
<script setup>
const showModal = ref(false);
async function loadModal() {
// Load the Modal component on command
await prefetchComponents('Modal');
}
</script>
With this implementation, we’ll start loading the Modal
component as soon as the user puts their mouse over the button that will show it. This might only be a fraction of a second before they click the button, but that speed up might be all we need to vastly improve the UX.
Of course, you can trigger this prefetch however you want! For example, if the button lives further down the page, you could use an intersection observer to trigger the load when the button scrolls into the viewport.
There are a few ways to make components asynchronous (which puts them in their own chunks), beyond using them with the Lazy*
prefix.
If you want to create global components, you can do it using the *.global.vue
suffix, which makes them async
by default. You can also use the /components/global
directory. By default, Nuxt loads all components inside of that directory as global
components, and makes them async
as well.
You can also configure a specific directory to always load components as async components. This is done through the components
option in nuxt.config.ts
:
export default defineNuxtConfig({
components: [
{
path: '~/components/async',
isAsync: true,
},
'~/components'
]
})
When isAsync
is set to true
, the component will always be loaded asynchronously, even if there is no Lazy*
prefix used. This gives us the same behaviour as adding the prefix, but we don’t have to remember to write it that way.
This can be really useful if we have a large component that we always want to load asynchronously and don’t want to accidentally load synchronously.
You could also use the pattern
field to specify a custom *.async.vue
suffix:
export default defineNuxtConfig({
components: [
{
path: '~/components',
pattern: '*.async.vue',
isAsync: true,
},
'~/components'
]
})
There are more configuration options you can find here.