22 April 2025

How To Optimize Images in Next.js with Next/Image Component

Blazity

Performance Optimization

In this article, we'll show why next/image is an excellent tool for managing images and solving issues like slow performance, high data usage, and slow rendering.

Blazity team
Blazity team
<p>How To Optimize Images in Next.js with Next/Image Component</p>

The Expert Guide to Next.js Performance Optimization

This article is part of The Expert Guide to Next.js Performance Optimization, a comprehensive Ebook you can download for free here.


The guide covers every aspect of building fast, scalable web apps with Next.js, including:


- Code Splitting

- Streaming, Suspense & Hydration

- Next/Image Component

- Third-Party Scripts

- Font Optimization

- Rendering Strategies

- Core Web Vitals

- Infrastructure

- Real-life Examples

- How to Measure Web Performance

- Bundle Analysis.

The Expert Guide to Next.js Performance Optimization

This article focuses on image optimization, a key factor in improving load times, reducing layout shifts, and boosting your app’s performance metrics.


We'll show why next/image is an excellent tool for managing images and solving issues like slow performance, high data usage, and slow rendering. You'll also discover how to use features like responsive sizing, image prioritization, placeholders, and blur effects to improve your visuals.

Implementation

So, before we start implementing the next/image, the first question is why we should use it instead of the traditional img tag. The answer is simple—it provides all the essential optimizations out-of-the-box and simplifies implementing the more advanced ones.


Using the next/image is generally a good choice. However, if your app has a lot of images, using that feature on some hosting providers can get expensive. For example, Vercel charges for each optimized image.


What if you don’t want to use the next/image for some reason?


It isn't the only way to optimize images in Next.js. While it's user-friendly and makes maintaining your app easier, you can always use other standard methods for image optimization, such as:

  • lazy loading below the fold,
  • setting fetchpriority for critical images,
  • decoding images asynchronously,
  • or setting srcset manually.


In fact, next/image uses these techniques under the hood but requires much less config, which makes it an excellent choice. For this reason, in this chapter, we’ll only focus on using the next/image.


Now, let's delve into some fundamental guidelines for optimizing images that help achieve optimal performance.

Prioritize critical images and lazy load non-critical

Managing how images load is crucial based on their importance and position on the page. For images that are immediately visible, known as "above the fold," you can use the priority attribute with the Next.js Image component to improve the loading strategy. This attribute instructs the browser to preload these images, deeming them high priority and skipping lazy loading.

Code

1import Image from "next/image";
2
3export const Page = () => (
4  <div>
5    <Image
6      src="/images/lcp-image.png"
7      alt="LCP Image"
8      priority
9      width={1000}
10      height={500}
11    />
12    {/* Below the fold content */}
13  </div>
14);

This approach is particularly useful for images contributing to the Largest Contentful Paint (LCP). The traditional img tag indeed has a similar attribute called "loading." But does setting it to "eager" achieve the same effect as the "priority" attribute in next/image?


What is the difference between loading=”eager” and the priority prop?


  • for plain HTML using loading="eager": When applied to an HTML img tag, this attribute ensures that the image downloads immediately as the browser processes the HTML. The eager attribute signals the browser to prioritize fetching the image.

<img src="path/to/image.jpg" alt="Description of image" loading="eager" />

  • for JSX and next/image component using the priority attribute: This attribute instructs Next.js to include a <link preload> tag at the top of your page. This approach is more aggressive than plain HTML’s loading="eager" because it prompts the browser to load the image even before the HTML is fully parsed, optimizing the loading process for critical resources.

Code

1import Image from 'next/image';
2
3export const MyComponent = () => {
4  return (
5    <div>
6      <Image
7        src="/path/to/image.jpg"
8        alt="Description of image"
9        width={500}
10        height={300}
11        priority
12      />
13    </div>
14  );
15};

Some notes here: next/image also supports loading "eager|lazy," just like plain HTML. However, we advise applying it and recommend using the priority prop instead. Remember that using the loading prop on next/image is for advanced use cases and typically worsens performance.

Worth knowing

In most cases, it doesn't make a noticeable difference. However, when pro grammatically adding images to the DOM, you should pay extra attention to the decoding property.


Using decoding="sync" ensures that the <img/> tag and the surrounding <div> remain hidden until the decoding is complete. However, this approach will block other unrelated rendering processes for the next tick(s), which is the intended visual behavior.


On the other hand, decoding="async" doesn't block other resources from (re)rendering. It also does not guarantee that the <img/> and surrounding <div> will be visible only after the image has finished loading.

For example, if the <div> has an orange background, you will see a flash of orange, which is likely not the intended visual behavior. Choose the option that best fits your needs when programmatically adding elements to the DOM!

Image placeholders and blur

You may want to display the placeholder while the images load. It can be easily achievable using the `placeholder` property from next/image. The placeholder can be one of the following values:


  1. empty - The default value set for all the images. This means that no placeholder is set and there is no visual representation of the loading state.
  2. blur - When you specify it, the app displays a blurry version of the image during loading. When it comes to the dynamic images - here, along with the placeholder=”blur” you must use the blurDataURL property and specify the blurhash manually if your image hosting provider supports returning it from the server.


The placeholders indicate to the user that the site is not broken and that something will be put in place for the temporary value in the image container. They also prevent some layout shifts.

Use modern image formats

Using modern image formats like WebP and AVIF in your web applications can significantly enhance performance by reducing file sizes and improving page load times. In the context of Next.js, this process is being streamlined with the Image component. It automates the delivery of images in the most efficient format supported by the user's browser.

Worth Knowing

Progressive JPGs load in stages, initially displaying a full but blurry image that gradually sharpens, unlike standard baseline JPGs, which load from top to bottom.


In case of:

  • very slow internet connections (e.g., apps targeting developing countries),
  • large and detailed images,
  • or downloadable content.

Progressive JPGs can be a better choice than WebP due to their perceived loading speed advantage.


How Automatic Image Optimization Works in Next.js?



Using the next/image module component automatically converts images into more efficient formats, depending on browser support. This means that even if the original image is in JPEG or PNG format, Next.js can serve it in a format that results in smaller file sizes and (in most scenarios) faster loads.

Code

1import Image from 'next/image'; export const Component = () => (
2<div>
3  <image src="https://example.com/path/to/your/image.jpg" alt="example" priority />
4</div>
5);
6

So, if we quickly revisit the previous example, it's important to note that "example.png" will be served as a WebP image instead of a PNG file.

Code

1import Image from 'next/image'; const Page = () => (
2<div>
3  <image src="/images/example.png" alt="example" priority />
4  <image src="/images/example.png" alt="example" />
5</div>
6); export default Page
7

Worth Knowing

By default, Next.js will use only WebP format.

AVIF has to be enabled explicitly. It is important to actively pick the best im age format for your application, as this is often overlooked.

Fine-Grained Image Format Control


While automatic image optimization is beneficial, you might want to disable this behavior in some situations.

For example:

  • certain applications may require the original format for compatibility reasons,
  • or you might want to maintain specific visual qualities that the optimization process could alter.


Here are some scenarios when automatic optimization might not be suitable:


  • Compatibility with Legacy Systems: If your application interfaces with older systems that do not support modern image formats like WebP (Next.js serves optimized images as WebP by default) or AVIF, you may need to keep the images in their original format.
  • Preserving Image Quality: It might be better to use the original format in cases where the original image has specific qualities (like color fidelity or transparency) that might be affected by conversion.
  • Already Optimized Images: If you have images that are already optimized (for example, images generated by tools like ImageOptim or TinyPNG), serving them in their original format may be preferable to avoid unnecessary processing and potential quality loss.
  • Vector Images (SVG): Automatic image optimization isn’t ideal for SVGs because it can remove the advantages of using vector graphics, such as scalability and maintaining the original quality. Converting SVGs to raster formats like WebP during optimization can remove important elements. Also, SVGs are typically small and don’t need aggressive optimization.
  • Large Amount of Images: Automatic optimization can be expensive when handling a large number of images. In these cases, manual control over image formats and optimizations could be more efficient, especially cost-efficient.


To disable automatic image optimization in Next.js, you can use the unoptimized prop in the next/image component. This prop instructs Next.js to serve the image in its original format without any conversion or optimization.

Code

1import Image from 'next/image'
2
3export const Component = () => {
4  return (
5    <div>
6      <Image
7        src="/path/to/your/image.jpg" // Original image path
8        alt="Description of image"
9        width={500}
10        height={300}
11        unoptimized // Disable automatic optimization
12      />
13    </div>
14  )
15}

Set sizes Appropriately

Correctly sizing images is vital for optimal web performance, minimizing layout shifts, and ensuring visuals appear sharp on various devices. The next/image simplifies this task, offering several methods to specify image dimensions and behavior depending on specific requirements.

Three Approaches to Image Sizing in Next.js:


1. Automatic Sizing for Static Imports

Next.js can automatically determine the dimensions of locally stored images imported statically, helping the browser reserve the space needed before the image loads. This approach prevents layout shifts and ensures smooth rendering.

Code

1import Image from 'next/image';
2import teamPhoto from '../public/team.jpg'; // Static import of an image
3
4export const Home = () => {
5  return (
6    <div style={{ width: '500px', height: 'auto' }}>
7      <h1>Meet Our Team</h1>
8      <Image
9        src={teamPhoto}
10        alt="Our team standing together"
11      />
12    </div>
13  );
14}

2. Explicit Sizing with Width and Height Properties

Set explicit width and height for remote images or fixed dimensions to help the browser allocate the right space. This straightforward method ensures images are rendered at the intended size, avoiding unexpected layout shifts.


Code

1import Image from 'next/image';
2
3export const ProductPage = () => {
4  return (
5    <div style={{ width: '800px', margin: 'auto' }}>
6      <h2>Featured Product</h2>
7      <Image
8        src="https://example.com/product.jpg"
9        alt="Newest model of a high-tech gadget"
10        width={800}
11        height={600}
12      />
13    </div>
14  );
15};

3. Implicit Sizing with the Fill Property

The fill property allows an image to expand and cover its parent container, making it ideal for dynamic sizing or adapting to varying display dimensions. It's particularly useful when the exact image size isn't known in advance and must adjust to the layout dynamically.

Code

1import Image from 'next/image';
2
3export const Banner = () => {
4  return (
5    <div style={{ width: '100%', height: '400px', position:
6    'relative' }}>
7      <Image
8        src="/banner.jpg"
9        alt="Promotional banner image"
10        fill
11        style={{ objectFit: 'cover' }} // Ensures the image covers
12        the full area of the container
13        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw,
14        33vw"
15      />
16    </div>
17  );
18};
19

Why use the sizes attribute with the fill layout?

The sizes attribute is essential when using the fill layout, as it tells the browser which image size to load based on the screen resolution.


Without the sizes attribute, the browser may download a larger image than necessary, leading to longer load times and higher data usage. By setting conditions such as (max-width: 768px) 100vw, (max-width: 1200px) 50vw, or 33vw, you ensure the browser loads an appropriately sized image, preventing excessive bandwidth usage and improving performance.


Note: Without the 33vw size setting, the browser would select an image three times wider than needed. Since file size grows with the square of the width, the user would end up downloading an image nine times larger than necessary. 


Next.js can automatically handle resizing for full-width images, but explicitly defining sizes can improve performance.

4. deviceSizes and imageSizes in the project configuration


If you know the types of devices your users have, you can configure deviceSizes and imageSizes to ensure images are resized correctly for optimization. These configuration properties serve different purposes, which are explained below:


  • deviceSizes: Defines breakpoints for full-width images, with a default set of [640, 750, 828, 1080, 1200, 1920, 2048, 3840]. Use this when targeting specific device widths. These breakpoints help Next.js choose the appropriate image size based on the device’s viewport width.
  • imageSizes: Used for images smaller than the viewport width (when using the 'sizes' prop). The default values are [16, 32, 48, 64, 96, 128, 256, 384], and each must be smaller than the smallest value in deviceSize.


In summary, sizing images appropriately with Next.js's Image component prevents layout shifts and optimizes loading times and bandwidth usage, ensuring your website performs well across all devices and resolutions.

Image quality

Setting the right image quality is essential to optimizing your web application's performance and user experience. While the default quality setting of 75 typically strikes a good balance between file size and visual experience, there are situations where adjusting this parameter can further enhance your website.


Pros & Cons of Manipulating Image Quality:


Pros:

+ Enhanced Visuals: Higher quality ensures that images look crisp and detailed on high-definition displays or when they are a focal point (like in a photography portfolio).

+ Brand Image: High-quality images can reflect more professionally on your brand, which might be critical in e-commerce and digital marketing.

+ User Experience: Sharper, more vibrant images can improve user engagement and satisfaction, potentially leading to longer site visits.


Cons:

- Longer Loading Times

- More Bandwidth Usage

- Visual Artifacts: Reduced quality can introduce noticeable imperfections like pixelation or compression artifacts, potentially impacting the image's overall visual appeal.


Here's how to explicitly set the image quality in Next.js to tailor the user experience based on your specific needs.

Code

1import Image from 'next/image';
2
3export const SimpleImage = () => {
4  return (
5    <Image
6      src="/path-to-your-image.jpg"  // Replace with your image path or URL
7      alt="A description of the image"
8      width={800}
9      height={500}
10      quality={80}  // Set image quality from 0 to 100. Default is 75.
11    />
12  );
13}
14

Considerations:

  • Assess Needs: Determine the importance of image quality vs. performance for your application. For instance, a blog with many images might prioritize lower quality for speed, whereas an art gallery website would benefit from higher-quality settings
  • Test Changes: Use tools like Lighthouse to measure how changes in image quality affect performance and loading times. Observe the balance between quality and performance to find the optimal setting for your use case.


Note:

These considerations apply to raster images (e.g., JPEG, WebP, PNG). The default loader generally does not optimize vector images (e.g., SVG) because they can be resized without losing quality. Additionally, SVG optimization is limited due to security concerns related to the format.


If you want to use next/image for SVGs, the unoptimized flag is recommended, but Next.js also turns optimization off automatically when the image’s src ends with “.svg.”

Keypoints

  1. Next <Image> component automatically converts your images to modern formats like AVIF or WebP. Still, you should always consider which image format best suits your needs.
  2. If there are no specific needs, use the default WebP format, which the next image provides by default for static images.
  3. Images with the largest LCP should be loaded with priority attributes.
  4. Images other than LCP but above the fold should always be assessed to determine whether the priority attribute is beneficial.
  5. Images located below the fold should have lazy loading enabled.
  6. Always include the sizes attribute when using the layout="fill" setting.
  7. Experimenting with different image qualities can yield optimal results.
  8. Use the unoptimized prop to skip optimization for specific images.
blazity comet

Get a quote

Empower your vision with us today
Brian Grafola, CTO at Vibes

“We are very happy with the outcomes and look forward to continuing to work with them on larger initiatives.”

Brian Grafola

CTO at Vibes

Trusted by
Solana logoVibes logoArthur logoHygraph logo
The contact information is only used to process your request. By clicking send, you agree to allow us to store information in order to process your request.