Optimization Best Practices
Optimizing your static assets is crucial for performance, user experience, and SEO in Nuxt applications.
Image Optimization
Size Optimization
Compression Levels:
- JPEG: Quality 75-85 for photos
- WebP: Quality 80-85 for web delivery
- PNG: Use tools like pngquant for lossy compression
Tools:
# ImageMagick
convert input.jpg -quality 85 -strip output.jpg
# pngquant
pngquant --quality=65-80 input.png -o output.png
# Sharp (Node.js)
sharp('input.jpg')
.jpeg({ quality: 85 })
.toFile('output.jpg');
Responsive Images
Provide multiple sizes for different viewports:
<template>
<NuxtImg
src="/images/hero-background.jpg"
sizes="xs:100vw sm:100vw md:100vw lg:1200px xl:1400px"
:modifiers="{ format: 'webp', quality: 85 }"
alt="Hero background"
/>
</template>
Manual responsive images:
<picture>
<source
media="(min-width: 1200px)"
srcset="/images/hero-1920w.webp"
type="image/webp"
/>
<source
media="(min-width: 768px)"
srcset="/images/hero-1200w.webp"
type="image/webp"
/>
<img
src="/images/hero-640w.jpg"
alt="Hero background"
loading="lazy"
/>
</picture>
Lazy Loading
<template>
<!-- Native lazy loading -->
<img src="/images/product.jpg" loading="lazy" alt="Product" />
<!-- NuxtImg with lazy loading -->
<NuxtImg
src="/images/product.jpg"
loading="lazy"
placeholder
/>
</template>
Modern Formats
Priority order:
- AVIF (best compression, limited support)
- WebP (good compression, wide support)
- JPEG/PNG (fallback)
<picture>
<source srcset="/images/hero.avif" type="image/avif" />
<source srcset="/images/hero.webp" type="image/webp" />
<img src="/images/hero.jpg" alt="Hero" />
</picture>
Video Optimization
Compression
# H.264 for MP4 (compatibility)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output.mp4
# VP9 for WebM (efficiency)
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 output.webm
Adaptive Streaming
For longer videos, consider HLS or DASH:
# Generate HLS playlist
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium \
-hls_time 10 -hls_playlist_type vod \
output.m3u8
Video Attributes
<video
preload="metadata"
poster="/images/video-poster.jpg"
controls
>
<source src="/videos/demo.webm" type="video/webm" />
<source src="/videos/demo.mp4" type="video/mp4" />
</video>
Font Optimization
Subsetting
Remove unused characters:
# Subset font to Latin characters only
pyftsubset input.ttf \
--output-file=output.woff2 \
--flavor=woff2 \
--unicodes=U+0020-007F,U+00A0-00FF
Font Loading Strategies
/* Nuxt app.vue or layout */
<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-400-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap; /* or 'fallback' for better performance */
}
</style>
Variable Fonts
Use variable fonts to reduce requests:
@font-face {
font-family: 'Inter Variable';
src: url('/fonts/inter-variable.woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap;
}
SVG Optimization
SVGO
# Install
npm install -g svgo
# Optimize single file
svgo input.svg -o output.svg
# Batch optimize
svgo -f ./icons -o ./icons-optimized
Manual Optimization
<!-- Before: 2KB -->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<!-- Complex paths with unnecessary precision -->
</svg>
<!-- After: 0.5KB -->
<svg viewBox="0 0 24 24" fill="none">
<!-- Simplified paths -->
</svg>
Inline vs External
Inline for:
- Icons used once or twice
- Critical above-the-fold graphics
- Styling with CSS
External for:
- Icons used multiple times
- Large illustrations
- Cacheable assets
Performance Monitoring
Nuxt DevTools
<script setup>
if (process.dev) {
// Monitor asset loading
console.log('Asset loaded:', Date.now());
}
</script>
Lighthouse Metrics
Target metrics:
- Largest Contentful Paint (LCP): < 2.5s
- First Input Delay (FID): < 100ms
- Cumulative Layout Shift (CLS): < 0.1
- Total Blocking Time (TBT): < 300ms
Bundle Analysis
# Nuxt analyze command
npm run build -- --analyze
CDN Integration
Cloudflare Images
// nuxt.config.ts
export default defineNuxtConfig({
image: {
cloudflare: {
baseURL: 'https://example.com/cdn-cgi/image/'
}
}
})
Usage:
<NuxtImg
provider="cloudflare"
src="/images/hero.jpg"
:modifiers="{ width: 1200, quality: 85, format: 'webp' }"
/>
Custom CDN
export default defineNuxtConfig({
app: {
cdnURL: process.env.CDN_URL || 'https://cdn.example.com'
}
})
Caching Strategies
Service Worker Caching
// service-worker.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('assets-v1').then((cache) => {
return cache.addAll([
'/images/logo.svg',
'/fonts/inter-400-regular.woff2'
]);
})
);
});
HTTP Headers
In your deployment configuration:
# nginx.conf
location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.(woff|woff2|ttf|otf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Automated Optimization Pipeline
Build Script Example
// scripts/optimize-assets.js
import sharp from 'sharp';
import { glob } from 'glob';
const images = await glob('public/images/**/*.{jpg,png}');
for (const image of images) {
// Generate WebP
await sharp(image)
.webp({ quality: 85 })
.toFile(image.replace(/\.(jpg|png)$/, '.webp'));
// Generate AVIF
await sharp(image)
.avif({ quality: 80 })
.toFile(image.replace(/\.(jpg|png)$/, '.avif'));
}
Package.json Script
{
"scripts": {
"optimize:images": "node scripts/optimize-assets.js",
"prebuild": "npm run optimize:images",
"build": "nuxt build"
}
}
Best Practices Checklist
- Images compressed to appropriate quality
- Multiple image formats provided (WebP, AVIF)
- Responsive images for different viewports
- Lazy loading for below-the-fold images
- Fonts subsetted and using font-display
- SVGs optimized and minified
- Videos compressed with multiple formats
- CDN configured for asset delivery
- Proper caching headers set
- Performance metrics monitored regularly
Resources
- Nuxt Image Documentation
- Web.dev Image Optimization
- ImageOptim - GUI tool for macOS
- Squoosh - Online image optimization
Next Steps
- Naming Conventions - Maintain consistency in your optimized assets
- Format Conversion - Convert to optimal formats