How to Get Responsive Images Right with Nuxt Image and Astro
This guide applies Responsive Images with srcset and sizes to two popular frameworks, within Image & Media Optimization. Both Nuxt Image (@nuxt/image) and Astro's built-in <Image> and <Picture> components generate srcset, convert formats and set dimensions for you. That removes most of the manual work — but their defaults are tuned for the common case (below-the-fold images), and the one image that matters most, the LCP hero, needs explicit settings to avoid being lazy-loaded, deprioritised or given the wrong sizes.
The recurring failure pattern on Nuxt and Astro sites is a correctly generated, beautifully responsive hero that loads late because it inherited loading="lazy", or loads too large because sizes defaulted to 100vw in a layout where it renders at half the width.
Rapid Diagnosis
- Inspect the rendered hero markup. Check
loading,fetchpriority,sizesand whether a<link rel="preload">exists in the head. - Check
currentSrcat mobile and desktop widths. Is the chosen variant close to rendered width × DPR? - Check global presets. Nuxt Image
presetsor Astro defaults may applyloading="lazy"everywhere. - Check the provider. Nuxt Image's IPX provider transforms on request in development and at build or runtime in production; a misconfigured provider can serve originals.
Root Cause Analysis
1. Lazy loading on the LCP image. Global presets or component defaults apply loading="lazy" to every image.
2. Wrong sizes. Without a correct sizes, the browser assumes 100vw and over-fetches for images in grids or columns.
3. No priority signal. Neither framework knows which image is the LCP unless you tell it.
4. Formats not configured. AVIF may be disabled by default in some setups for build-time reasons.
Step-by-Step Resolution
1. Nuxt Image: mark the hero explicitly
<template>
<NuxtPicture
src="/images/hero.jpg"
:img-attrs="{ fetchpriority: 'high', alt: 'Trail runner on a ridge' }"
sizes="100vw md:100vw lg:1200px"
format="avif,webp"
width="1200" height="675"
loading="eager"
preload
/>
</template>
<!-- trade-off: the preload prop adds a <link rel="preload"> per instance; use it
on the single LCP image per page only. -->
// nuxt.config.ts — sensible defaults for everything else.
export default defineNuxtConfig({
image: {
format: ['avif', 'webp'],
screens: { sm: 480, md: 768, lg: 1200, xl: 1600 },
presets: { card: { modifiers: { fit: 'cover', quality: 70 }, loading: 'lazy' } },
},
});
// trade-off: AVIF generation increases build or first-request transform time;
// cache transformed images at the CDN.
2. Astro: use Picture with explicit priority
---
import { Picture } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Picture src={hero} formats={['avif', 'webp']} widths={[480, 800, 1200, 1600]}
sizes="(max-width: 800px) 100vw, 1200px" alt="Trail runner on a ridge"
loading="eager" fetchpriority="high" />
<!-- trade-off: Astro generates every width × format at build; large widths lists
on many images increase build time. -->
3. Set sizes from the layout, per usage
For grid cards: sizes="(max-width: 600px) 50vw, (max-width: 1200px) 33vw, 300px". Each component usage should pass the sizes matching where it renders.
4. Keep below-the-fold images lazy with dimensions
Let the default lazy behaviour apply to everything else, and make sure width and height (or aspect ratio) are always set so nothing shifts.
Verification
At mobile and desktop widths, the hero request should start during HTML parsing at High priority, initiated by the preload, and currentSrc should be a sensibly sized AVIF. Lighthouse's LCP image audits ("Largest Contentful Paint image was lazily loaded", "Preload LCP image") should pass. In RUM, resourceLoadDelay for the hero should be small.
Worked Example: A Nuxt Content Site
A documentation site on Nuxt used <NuxtImg> everywhere with a global preset setting loading="lazy", including on the landing page's hero illustration. LCP p75 on mobile was 3.2s, with 1.1s of load delay. Adding loading="eager", fetchpriority="high" and preload to the hero, and correcting sizes for the two-column layout (it had defaulted to 100vw, over-fetching 2x on desktop), brought LCP p75 to 2.0s and cut hero bytes by 45% on desktop.
Providers and Production Caching
Both frameworks can transform images at build time (static) or on request (server or CDN provider). For static Astro builds, images are generated during the build and served as files. Nuxt Image's default IPX provider transforms on request in server deployments; in production, put a CDN in front with long caching for transformed URLs, or switch to an image CDN provider (Cloudflare, Cloudinary, imgix, Vercel) so transforms happen at the edge. Whichever you use, confirm production responses carry Cache-Control: public, max-age=31536000, immutable and that the first request for a new size is not slow for every user — a common cause of slow LCP after deploys on server-transformed setups.
Auditing Every Image Component Usage
Framework components make it easy to get one image right and hard to see whether all of them are. Add a lint or test step that scans templates for image component usages and flags: usages without sizes, more than one usage per page with preload, loading="lazy" on components inside hero or above-the-fold layout slots, and remote images without explicit dimensions. A small script over the built HTML can do the same at the output level: for each page, list images with fetchpriority="high" (should be at most one), images without width/height, and the LCP candidate's loading attribute. Run it in CI so a new template cannot silently regress the hero.
Common Mistakes
- Global lazy presets applied to the hero.
- Copying
sizes="100vw"into every usage. - Preloading several images. Bandwidth contention delays the real LCP image.
- Serving IPX transforms without CDN caching. Every visitor pays transform latency.
Edge Cases
Remote images. Both frameworks require allow-listing remote domains for transformation; otherwise they pass URLs through untransformed.
SVG images. Framework components usually pass SVGs through; use <img> with dimensions directly.
Art direction. Astro's <Picture> and Nuxt's <NuxtPicture> focus on formats and widths; art-directed crops still need manual <picture> with <source media>.
Content collections and Markdown. Configure Markdown image handling so content images get the same treatment as template images.
FAQ
Does Nuxt Image add a preload automatically?
Only when you set the preload prop. Add it to the LCP image only.
Does Astro lazy-load by default?
Astro's image components default to lazy loading for images, so set loading="eager" and fetchpriority="high" on the hero.
Should I use Picture or Image?
Picture emits <picture> with multiple formats (AVIF and WebP with fallback); Image emits a single <img> in one format. For heroes and important images, Picture gives the best format coverage.
How do I pick widths?
Cover the rendered sizes at DPR 1–3 for each usage. Framework screens/widths settings let you define a ladder once; override per usage when an image renders much smaller.
Do these components prevent CLS?
They emit width and height when they know the image's dimensions (local images, or remote images with explicit dimensions), which prevents CLS. For remote images without dimensions, pass them explicitly.
Can I use an external image CDN with these components?
Yes — both support providers or custom loaders for image CDNs, letting the CDN do transforms while the component generates markup.
What about images in Nuxt Content Markdown?
Nuxt Content can render Markdown images through a ProseImg component; override it to use NuxtImg with lazy loading, dimensions and sensible sizes, so content images get the same optimisation as template images.
Related
- Generating responsive images at build time — the underlying pipeline.
- Fixing slow LCP in Vue and Nuxt apps — the wider Nuxt LCP picture.
- Choosing Astro client directives — Astro performance beyond images.