
Ultimate guide to Nuxt performance
Fonts, Images, HTML compression, Hydration & more!
Ali Sokkar
Vue & Nuxt engineer
Nuxt has a ton of modules for different purpose, whether you want to add analytics, improve security, integrations with CMS platforms, libraries integrated with Nuxt, and more.
But the most interesting part for me are the ones that improve your performance out of the box. See Nuxt itself has some performance features that we will dive deep into first, but the community has provided unique out-of-the-box modules to automatically handle some of the general elements of performance.
Nuxt features
Let's not get ahead of ourselves and look into the built-in features to improve performance in an intelligent way.
Lazy Hydration
Nuxt has multiple utilities ready to use including defineLazyHydrationComponent that simplifies the implementation of lazy hydration.
In modern javascript, we generally utilize the Intersection Observer javascript constrcutor to detect if an element is currently visible in the users' viewport. Here's a basic example of how it works.
// the function that gets called when an intersection is detected
const callBack = (entries) => {
isVisible = entries[0].isIntersecting
}
// the container of the item we want to observe
const container = document.querySelector('.container')
const options = {
root: container,
threshold: 1
}
const observer = new IntersectionObserver(callBack, options)
// the item we want to observe
const target = document.querySelector('.target')
observer.observe(target)
The core idea of the implementation is generally simple. But imagine wriing this piece of code for every container and alternating it to each use case?
That definitely isn't the ✨ magical ✨ Nuxt way.
Instead, Nuxt has provided us with a utility called defineLazyHidrationComponent that abstracts away the complexity of the implementation and provides a simple API to use.
<script setup lang="ts">
const LazyHydrationMyComponent = defineLazyHydrationComponent(
'visible', // THIS is the magic word
() => import('./components/MyComponent.vue')
)
script>
<template>
<div>
<LazyHydrationMyComponent :hydrate-on-visible="{ rootMargin: '100px' }" />
div>
template>
Did you see what it did there? It created a lazy hydration component that will only hydrate when it becomes visible in the viewport. You're importing the component as a dynamic import, so it won't be part of the initial bundle. Only when the component is about to enter the viewport by the 100px we set, it will be fetched and hydrated.
This is pretty insane on its own, but it gets better...
Not only does it support the import of the component lazily when it becomes visible, but it also supports other triggers like:
idle: Hydrate when the browser is idle. If a component is very essential and needs to load quickly, but not immediately, this is a great option.mediaQuery: Hydrate when a specific media query matches (eg:(min-width: 1024px)).interaction: Hydrate on user interaction (click, hover, focus, etc.).time: Hydrate after a specific time delay (eg:5000for 5 seconds).if: Hydrate based on a custom condition (eg: a reactive variable).never: Never hydrate (useful for static content).
This means you can have full control over when and how your components are hydrated, leading to significant performance improvements, especially for large applications with many components. Could you believe that this is already out of the box in Nuxt 4? We haven't even started with the modules yet!
But what we talked about is granular lazy hydration. Nuxt also provides Lazy Components. How does it work?
Lazy Components
When importing a component, all you need to do is add Lazy to the start of the component's name and tell it when to hydrate:
<template>
<div>
<MyComponent />
<LazyMyComponent hydrate-on-visible />
div>
template>
This is a further abstraction of Nuxt's defineLazyHydrationComponent which itself is an abstraction of Vue's Async Components. This means that you can easily convert any component to a lazy-loaded component with minimal effort.
Now we know how to order the rendering / hydration of components in your website. But what happens when you're building a very dynamic app? Some pages are dynamic and others are static. You might've thought of hydrate-never, but Nuxt had something better in mind...
Hybrid rendering
You have an app that has dynamic data, static content, essential pages. You're confused on whether it'd be better to render your app on the server or as an SPA. Well with Nuxt you don't have to choose!
Nuxt allows you to choose the optimal rendering method based on the content. You can choose the Hybrid method! In addition to Nuxt rendering in Universal Rendering by default which combines SSR Rendering with SPA to render the first page on the server then Nuxt hydrates the rest of the app in the background, Hybrid Rendering allows to to combine multiple rendering techniques based on the routes.
export default defineNuxtConfig({
routeRules: {
// Homepage pre-rendered at build time
'/': { prerender: true },
// Products page generated on demand, revalidates in background, cached until API response changes
'/products': { swr: true },
// Product pages generated on demand, revalidates in background, cached for 1 hour (3600 seconds)
'/products/**': { swr: 3600 },
// Blog posts page generated on demand, revalidates in background, cached on CDN for 1 hour (3600 seconds)
'/blog': { isr: 3600 },
// Blog post page generated on demand once until next deployment, cached on CDN
'/blog/**': { isr: true },
// Admin dashboard renders only on client-side
'/admin/**': { ssr: false },
// Add cors headers on API routes
'/api/**': { cors: true },
// Redirects legacy urls
'/old-page': { redirect: '/new-page' }
}
})
This allows for maximal flexibility to deploy all kinds of pages in your app, allowing you to create a super-app that allows all kinds of content to render as well as it should.
There are tons of optimization techniques available directly within Nuxt and it is definitely worth exploring which is why I encourage you to read their article on it: https://nuxt.com/docs/4.x/guide/best-practices/performance
But enough of the core features, let's talk about the more targeted optimization techniques with modules!
Nuxt Performance Modules
There are multiple core modules developed by the Nuxt team and some community modules that further improves the performance of your app, the Nuxt article referenced above covers the core modules (@nuxt/image, @nuxt/fonts, @nuxt/scripts). What I want to focus on here are the community modules:
Nuxt Booster
Nuxt Booster works by initializing only the necessary resources located in the current viewport when the page is loaded. This includes images, fonts and the js-modules.
This module provides a holistic approach to intelligently load the necessary viewport related resources to reduce FCP, DCL, TTI, TBT and CLS. And it doesn't re-invent the wheel. They've adapted Lazy Loading concepts and utilize @nuxt/image as a base to retrieve optimized image resolutions for the picture and image components and add some new stuff to obtain a holistic solution.
What are those metrics?
- FCP (First Contentful Paint): Measures the time from when the page starts loading to when any part of the page's content is rendered on the screen.
- DCL (DOMContentLoaded): Measures the time it takes for the initial HTML document to be completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.
- TTI (Time to Interactive): Measures how long it takes for the page to become fully interactive, meaning that the user can interact with the page without any delays.
- TBT (Total Blocking Time): Measures the total amount of time that the main thread is blocked and unable to respond to user input.
- CLS (Cumulative Layout Shift): Measures the sum of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page.
Fun fact about the FCP & TTI metric: Research on the UX of latency has been conducted to conclude that 5 seconds is the maximum amount of time a user is willing to wait before they start abandoning the site. Additionally, an FCP of around 200ms is preceived by the human brain as instantaneous. Chrome's Lighthouse tool sets a target of 0s-1.8s for FCP to be considered fast and 0s-3.8s for TTI to be considered fast.
This is due to other excusable latencies such as network delays, server response times, etc. that are out of the control of the browser. Which makes people generally accept a bit more delay than the 200ms threshold and it will still feel instantaneous. They already assume a 0.5-1s delay due to these uncontrollable factors, so when it takes an additional second, it still feels acceptable.
Why does it matter?
Because loading unnecessary resources on initial load can significantly impact your site's performance, leading to longer load times and a poor user experience. By only loading the resources that are needed for the current viewport, Nuxt Booster helps to reduce the amount of data that needs to be transferred, resulting in faster load times and improved performance metrics.
Learn more on how it works and how to set it up here: https://basics.github.io/nuxt-booster/
Partytown
Partytown is a library that helps you to move third-party scripts to a web worker, so they don't block the main thread. This is especially useful for improving performance on pages that rely heavily on third-party scripts, such as analytics, ads, and social media widgets.
Why does it matter?
Because third-party scripts can significantly impact your site's performance by blocking the main thread, leading to increased load times and a poor user experience. By offloading these scripts to a web worker, Partytown allows your main thread to remain responsive, resulting in faster load times and improved performance metrics.
Learn more on how it works and how to set it up here: https://github.com/nuxt-modules/partytown/
HTML Validator
The HTML Validator module helps you to ensure that your HTML is valid and follows best practices. It uses the Nu Html Checker to validate your HTML during development and build time.
What is the Nu Html Checker?
The Nu Html Checker (v.Nu) is a tool that checks HTML documents for conformance to HTML and XHTML standards. It can be used to identify errors and warnings in your HTML code, helping you to improve the quality of your markup.
Why does it matter for performance?
While often overlooked, valid HTML is a cornerstone of performant web pages. When browsers encounter invalid HTML, they have to switch to error-correcting modes (often called "tag soup" parsing), which consumes additional CPU cycles and increases parsing time.
Furthermore, broken HTML structure—like unclosed tags or improper nesting—can lead to:
- Layout Thrashing: The browser may struggle to calculate layout efficiently, causing multiple reflows.
- Unpredictable Rendering: Different browsers may handle errors differently, leading to visual glitches that might force you to add performant-heavy CSS hacks.
- Accessibility Issues: Screen readers rely on semantic structure. If the structure is broken, the "performance" of the user utilizing assistive technology is severely degraded.
By using the HTML Validator module, you catch these issues during development (hydration errors, anyone?), ensuring your final DOM is lean, clean, and easily parsable by the browser engine.
Conclusion
Performance isn't just about raw speed metrics; it's about the feeling of using your application. It's the difference between a user staying engaged or bouncing back to Google.
Nuxt 4 provides an incredible foundation with features like granular lazy hydration and hybrid rendering, allowing you to architect your application for speed from day one. When you combine that with powerful community modules like Nuxt Booster for asset optimization, Partytown for third-party script management, and HTML Validator for code quality, you're not just building a website—you're building a high-performance experience.
Start by measuring your current performance with tools like Lighthouse or PageSpeed Insights. Then, pick one of these techniques to implement today. You'll be surprised how much of a difference a single optimization can make.
Happy coding!