Skip to main content
Engineering7 min readMarch 3, 2026

Image Optimization in Nuxt: @nuxt/image and Beyond

A complete guide to image optimization in Nuxt — @nuxt/image setup, lazy loading, modern formats, responsive images, and improving Core Web Vitals with every image decision.

James Ross Jr.

James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

Images are the most common source of web performance problems and the most commonly ignored optimization opportunity. In most web applications, images account for 50-80% of the total page weight. Getting your image pipeline right is one of the highest-leverage performance investments you can make.

Nuxt makes this relatively easy with @nuxt/image, but using it correctly requires understanding what it does, what it does not do, and when the defaults need to be overridden.

Installing @nuxt/image

npx nuxi module add image

Configure the module in nuxt.config.ts:

image: {
  formats: ['avif', 'webp'],
  quality: 80,
  screens: {
    xs: 320,
    sm: 640,
    md: 768,
    lg: 1024,
    xl: 1280,
    xxl: 1536,
  },
  providers: {
    cloudflare: {
      baseURL: 'https://yourdomain.com',
    },
  },
},

The NuxtImg Component

Replace every <img> tag in your application with <NuxtImg>:

<NuxtImg
  src="/images/hero.jpg"
  alt="Hero image description"
  width="1200"
  height="630"
  format="webp"
  quality="85"
  loading="lazy"
/>

The component handles several things automatically:

  • Converts to modern formats (WebP, AVIF) based on browser support
  • Generates multiple sizes for responsive serving
  • Adds proper width and height attributes to prevent layout shift
  • Adds loading="lazy" by default (you override to "eager" for above-fold images)

For responsive images that change size across breakpoints, use sizes:

<NuxtImg
  src="/images/product.jpg"
  alt="Product name"
  sizes="100vw sm:50vw md:400px"
  :width="800"
  :height="600"
/>

This generates a srcset attribute with multiple image sizes. The browser downloads only the appropriate size for the current viewport. A user on a 375px mobile screen downloads a 375px image instead of an 800px image. That difference in download size is the difference between acceptable and excellent mobile performance.

The NuxtPicture Component

When you need more control over format fallbacks or want to serve different images for different art direction needs, use <NuxtPicture>:

<NuxtPicture
  src="/images/hero.jpg"
  alt="Hero description"
  :width="1200"
  :height="630"
  loading="eager"
  fetchpriority="high"
/>

NuxtPicture renders a <picture> element with <source> tags for each format, with the original as a fallback. Older browsers that do not support WebP or AVIF fall back gracefully to the original format.

Provider Configuration: Where Images Come From

For production applications, you rarely want to serve images from your own server. Use a CDN or image transformation service. @nuxt/image supports many providers out of the box:

Cloudflare Images is my preferred choice when already using Cloudflare:

image: {
  cloudflare: {
    baseURL: 'https://imagedelivery.net/your-account-hash',
  },
},

Imgix for more advanced transformations:

image: {
  imgix: {
    baseURL: 'https://your-subdomain.imgix.net',
  },
},

IPX (the built-in local provider) works for development and small-scale production when you do not have a CDN:

image: {
  ipx: {
    maxAge: 60 * 60 * 24 * 7, // 7 days cache
  },
},

Switch providers without changing your template code — just update the nuxt.config.ts provider configuration.

Critical Image Performance Patterns

Preload Hero Images

The LCP element is often a hero image. Preload it to tell the browser to fetch it immediately:

<script setup lang="ts">
useHead({
  link: [
    {
      rel: 'preload',
      as: 'image',
      href: '/images/hero.webp',
      type: 'image/webp',
    },
  ],
})
</script>

<NuxtImg
  src="/images/hero.jpg"
  loading="eager"
  fetchpriority="high"
  alt="Hero description"
  width="1200"
  height="630"
/>

Never use loading="lazy" on above-the-fold images. Lazy loading defers the image fetch, which is exactly wrong for your LCP element.

Prevent Layout Shift

Always provide width and height attributes. The browser uses these to reserve space before the image loads, preventing layout shift:

<!-- WRONG: no dimensions, causes layout shift -->
<NuxtImg src="/product.jpg" alt="Product" />

<!-- CORRECT: dimensions prevent layout shift -->
<NuxtImg src="/product.jpg" alt="Product" width="400" height="300" />

The aspect-ratio CSS property works as an alternative when you do not know the exact dimensions:

<div class="aspect-[4/3] overflow-hidden">
  <NuxtImg
    src="/product.jpg"
    alt="Product"
    class="w-full h-full object-cover"
  />
</div>

Blur Placeholders

For images below the fold, a blur placeholder improves perceived performance. The user sees a blurred low-quality version immediately while the full image loads:

<NuxtImg
  src="/images/blog-post.jpg"
  alt="Blog post image"
  width="800"
  height="450"
  placeholder
/>

The placeholder prop generates a tiny base64 image that shows while the full image loads. On a slow connection, this is a significant UX improvement — the user sees content rather than a blank space or jumping layout.

Handling CMS and External Images

When images come from a CMS or user-generated content, you need a different approach. You cannot rely on local paths — image URLs come from API responses.

Configure a domain allowlist for external images:

image: {
  domains: ['images.contentful.com', 'uploads.yourapp.com'],
  remotePatterns: [
    {
      protocol: 'https',
      hostname: '**.yourdomain.com',
    },
  ],
},

Use the src attribute with external URLs normally:

<NuxtImg
  :src="post.coverImage.url"
  :alt="post.coverImage.alt"
  :width="post.coverImage.width"
  :height="post.coverImage.height"
/>

If the CMS provides image width and height metadata (Contentful and Sanity both do), use those values. If not, define reasonable defaults and use CSS to constrain the display dimensions.

SVG: When to Not Use @nuxt/image

For SVG files, skip @nuxt/image. SVGs are already vector format and do not benefit from format conversion or resizing. Import them directly:

<script setup lang="ts">
import LogoIcon from '~/assets/icons/logo.svg?component'
</script>

<template>
  <LogoIcon class="w-8 h-8 text-blue-600" aria-hidden="true" />
</template>

Or for decorative SVGs that do not need to be styled:

<img src="/logo.svg" alt="Company logo" width="120" height="40" />

Background Images

CSS background images bypass @nuxt/image. For performance-critical background images, either switch to an <img> element with object-fit: cover, or use the CSS image-set function with WebP:

.hero {
  background-image: image-set(
    url('/images/hero.avif') type('image/avif'),
    url('/images/hero.webp') type('image/webp'),
    url('/images/hero.jpg') type('image/jpeg')
  );
  background-size: cover;
}

Measuring Impact

After implementing image optimization, measure the impact in Google PageSpeed Insights and the Network tab of Chrome DevTools. Look for:

  • Total image payload before and after (should drop significantly)
  • LCP improvement (faster images = faster LCP)
  • CLS score (should be 0 after adding dimensions to all images)

On one client project, implementing these patterns reduced total page weight by 65%, improved LCP from 3.8 seconds to 1.4 seconds, and fixed a CLS score that was causing ranking suppression. The work took about a day. The SEO recovery took about three weeks of Google re-crawling.

Images are not glamorous work, but the returns are real and measurable.


Want help auditing your Nuxt application's image performance or designing a CDN and image delivery strategy? Book a call: calendly.com/jamesrossjr.


Keep Reading