nuxt-image-optimizer
Nuxt Image Optimizer
Drop-in <o-img> component for Nuxt that serves resized, compressed images (WebP/AVIF/JPEG/PNG) from your own server, with no layout shift. Works with local files from public/ and with remote images from domains you allow.
Why use it
- No build step or external service. Images are optimized on first request with Sharp and cached.
- Fast first load. The original is served immediately while the optimized version is prepared in the background, so nobody waits for an encode.
- No layout shift. Original image size is read during SSR and exposed as CSS variables, so the page reserves the right space.
- Responsive.
srcset/sizeswith your own breakpoints, plus optional low-quality placeholders. - Stays fresh. Cached images are re-checked in the background and updated when the source changes.
Best suited to a single self-hosted Nuxt server. The cache lives in memory by default and is cleared on restart.
Requires Nuxt >=3.16 <5 and Node >=20.19.
Quick start
npm i nuxt-image-optimizer
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-image-optimizer'],
cachedImageOptimizer: {
// remote hosts you want to optimize (local /... paths always work)
domains: ['images.example.com'],
},
});
<o-img src="/photo.jpg" alt="A photo" srcset="320, 640, 1024" sizes="sm:100vw, 50vw" />
Component
<o-img
src="https://images.example.com/photo.jpg"
alt="A photo"
format="webp"
quality="75"
srcset="320, 640, 1024"
sizes="sm:100vw, 50vw"
placeholder="30, 320"
preload
@load="onLoad"
/>
| Prop | Description |
|---|---|
src |
Image URL. /... paths are read from public/; remote hosts must be in domains. Other hosts are shown as-is, unoptimized. |
format |
webp, avif, jpeg or png. Defaults to conversion.format. |
quality |
1–100. Defaults to conversion.quality. |
srcset |
Widths to generate, e.g. "320, 640, 1024". |
sizes |
Display width per breakpoint, e.g. "sm:100vw, 50vw". Keys come from the sizes config; a value without a key is the fallback. |
placeholder |
Low-quality preview shown first: "quality, width, height", e.g. "30, 320". Width and height are optional. |
preload |
Adds a preload link to the page head (use for above-the-fold images). |
Events: load:placeholder (placeholder loaded), load (final image loaded), error (final image failed; placeholder errors are ignored). They also fire if the image finished loading before Vue hydrated.
Styling hooks:
data-statusattribute:placeholder,completedorerror.- CSS variables
--original-width,--original-height,--aspect-ratio(original image, not the resized one).
Without alt, the image gets role="presentation".
How it works
- The component requests
/api/__cimgoptwith the image URL, size, format and quality. - If that variant is cached, it is served. Otherwise the original is served right away and an optimization task is queued.
- Later requests get the optimized file. Old entries are refreshed in the background and the least-used ones are evicted when the cache is full.
Invalid or non-allowed URLs get a 400, missing images a 404, unreachable remote images a 502. Remote redirects are rejected and fetches time out after 10 seconds.
Configuration
// defaults
{
cache: {
// if queue is full, new optimization tasks will be rejected
queueSize: 1000,
// pause between optimization works to reduce CPU load, format can be like '5s', '100ms', '1m', '2h'
queueTimeout: '5s',
// 'in-memory' for memory cache, or storage path for filesystem cache, like '.cache/oimgs'
storagePath: 'in-memory',
// max cache size, format can be like '100KB', '122MB', '14GB'
maxSize: '20MB',
// max number of cached image sizes (only width and height caching for ssr sizes probing)
sizeCacheMaxCount: 10000,
},
// background image refreshing, active only if main queue is empty
autoRefresh: {
// background refresh interval, cached images will be refreshed every 30 minutes by default
// WARN: avif images processing can be very slow (up to 10 seconds for large ones) and may occur queue overflows
maxAge: '30m',
// if queue is full, new refresh tasks will be rejected
queueSize: 10,
// pause between refresh works to reduce CPU load
queueTimeout: '5s',
},
conversion: {
// 'webp' | 'avif' | 'jpeg' | 'png', overwritten by component prop
format: 'webp',
// 1..100, overwritten by component prop
quality: 75,
// default width, overwritten by component prop
width: 4096,
// default height, overwritten by component prop
height: 4096,
// codec effort, 0..10 - higher is slower but smaller file sizes
cpuEffort: 6,
},
// media queries list to use in 'sizes' prop
// you can define your own keys, like '2xl'
sizes: {
xs: '(width < 320px)',
sm: '(width < 640px)',
md: '(width < 768px)',
lg: '(width < 1024px)',
xl: '(width < 1280px)',
xxl: '(width < 1536px)',
},
// allow‑list for remote hosts
domains: [],
}
Development
# Install dependencies
npm install
# Develop with playground
npm run dev
# Type check
npm run test:types
# Unit tests
npm test
# Lint
npm run lint
License
MIT
Related Packages
Automatically generates placeholders, srcset, sizes, and lazy loading to reduce...
Nuxt 4 module: responsive Bunny.net Optimizer images for Statamic image objects
Auto-generate Nitro defineRouteMeta (OpenAPI) for Nuxt server routes from JSDoc...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.0.7 | ^3.0 | MIT | |
| 0.0.6 | ^3.0 | MIT | |
| 0.0.5 | ^3.0 | MIT | |
| 0.0.4 | ^3.0 | MIT | |
| 0.0.3 | ^3.0 | MIT | |
| 0.0.2 | ^3.0 | MIT |