Performance
Introduction
Performance is critical for modern web applications—not only for delivering a smooth user experience but also for improving SEO rankings and reducing infrastructure costs. Nuxt 3, with its modern architecture built on top of Vue 3 and Vite/Nitro, provides a solid foundation for building high-performance web apps. However, to truly unlock its potential, developers need to follow best practices and implement strategic optimizations throughout the codebase.
This guide compiles essential tips and practical code examples to help you optimize your Nuxt 3 application for performance. From leveraging dynamic imports and managing state efficiently to optimizing asset delivery and enabling server-side caching, every section is crafted to reduce bundle size, improve load times, and enhance overall responsiveness.
1. Keep Pinia Stores Lean
Move business logic out of stores and into services or utils.
Example:
export const useCartStore = defineStore("cartStore", {
state: () => ({
cartItems: [],
}),
actions: {
async addToCart(context, item) {
const { addToCart } = await import("~/services/cartService");
return await addToCart(context, item);
},
},
});
2. Disable Component Auto Import
Update nuxt.config.ts:
components: {
global: true,
dirs: ['~/components/global'],
}
Import components dynamically:
const Example = defineAsyncComponent(() => import("@/components/Example.vue"));
3. Add Compression and Caching for Assets
Update nuxt.config.ts:
nitro: {
compressPublicAssets: true,
minify: true,
serveStatic: {
headers: {
'Cache-Control': 'public, max-age=31536000, immutable',
},
},
}
4. Add async and defer to Scripts
Use these attributes in custom scripts to avoid blocking page rendering.
5. Monitor Build Sizes
Run:
npm run build
Keep an eye on build output sizes.
6. Use transform to Optimize Payload
7. Use <ClientOnly> for Non-SEO Critical Components
<ClientOnly>
<NewsLetter />
</ClientOnly>
8. Use Pinia Effectively
9. Dynamic Loading of Services
Avoid using stores inside services. Instead, pass store instances from components or pages.
In Page/Component:
<script setup>
import { onMounted } from 'vue';
const myStore = useMyStore();
onMounted(async () => {
const { getItem } = await import('~/services/myService');
myStore.item = await getItem(myStore);
});
</script>
In Middleware:
export default defineNuxtRouteMiddleware(async (to, from) => {
if (to.path.startsWith("/protected")) {
const { useAuthStore } = await import("~/stores/auth");
const authStore = useAuthStore();
if (!authStore.isAuthenticated) {
return navigateTo("/login");
}
}
});
10. Use Exported Functions for Smaller Bundles
Preferred:
// utils.ts
export const foo = () => {
/* ... */
};
export const bar = () => {
/* ... */
};
// usage
import { foo, bar } from "./utils";
Avoid:
// MyClass.ts
export class MyClass {
foo() {
/* ... */
}
bar() {
/* ... */
}
}
// usage
import { MyClass } from "./MyClass";
const myInstance = new MyClass();
myInstance.foo();
11. SSR API Calls with useAsyncData
const { data } = await useAsyncData("results", async () => {
const { searchResult } = await import("@/services/algoliaService");
return searchResult();
});
12. Cache API Routes
Use cachedEventHandler:
13. Custom getCachedData in useAsyncData
const { data, error, pending } = await useAsyncData(
"getPopularKeywords",
async () => {
const { getPopularKeywords } = await import(
"@/services-optimized/builderService"
);
return await getPopularKeywords(runtimeConfig.public.builderApiKey);
},
{
immediate: true,
getCachedData(key) {
if (!debug) {
const cachedData =
nuxtApp.payload.data[key] || nuxtApp.static.data[key];
if (!cachedData) return;
const expirationTime = new Date(cachedData.fetchedAt);
expirationTime.setTime(expirationTime.getTime() + 1000 * 60 * 60);
const isExpired = expirationTime.getTime() < new Date().getTime();
if (isExpired) return;
}
},
}
);