Frontend Development

Optimizing and Rendering Images in Next.js 16 with the Image Component

Optimizing and Rendering Images in Next.js 16 with the Image Component

Images are often one of the biggest contributors to page size and loading time in modern web applications. A poorly optimized image can significantly increase Largest Contentful Paint (LCP), consume unnecessary bandwidth, and make a page feel slow—especially on mobile devices.

 

 

 

Next.js provides the built-in Image component, which handles much of the complexity of modern image optimization for you. In this article, we’ll explore how to use the Image component in Next.js 16, including:

  • Basic image rendering
  • Local and remote images
  • Responsive images
  • width and height
  • fill
  • sizes
  • Lazy loading
  • Image quality
  • Image formats
  • Remote image configuration
  • Background-like images

 

Why Use the Next.js Image Component?

Instead of using a normal <img /> tag:

<img src="/images/product.jpg" alt="Product" />

Nuxt provides:

import Image from "next/image";

<Image
  src="/images/product.jpg"
  alt="Product"
/>

The Image component can automatically optimize images by:

  • Serving appropriately sized images.
  • Automatically generating optimized image variants.
  • Supporting modern formats such as WebP and AVIF where appropriate.
  • Lazy-loading images by default when suitable.
  • Preventing layout shifts when dimensions are known.
  • Working with responsive image sizes.
  • Optimizing remote images when they are explicitly configured.

The important point is that you don’t have to manually create multiple versions of every image.

Basic Image Rendering

A simple locale image can rendered like this:

import Image from "next/image";

export default function Profile() {
  return (
    <Image
      src="/images/profile.jpg"
      width={400}
      height={400}
      alt="John Doe"
    />
  );
}

The image located at:

public/images/profile.jpg

The browser access it using:

/images/profile.jpg

So the project structure look like this:

my-app/
├── app/
│   └── page.jsx
├── public/
│   └── images/
│       └── profile.jpg
└── package.json

 

Why Height and width matters

You will commonly see:

<Image
  src="/images/product.jpg"
  width={800}
  height={600}
  alt="Product"
/>

These values describe the image’s intrinsic dimensions/aspect ratio.

They aren’t necessarily saying: “Always render this image at exactly 800 × 600 pixels.”

Instead, they allow Next.js and the browser to understand the image’s dimensions and reserve the appropriate space.

This helps prevent Cumulative Layout Shift (CLS).

For example: without dimensions

<img src="/product.jpg" alt="Product" />

the browser may initially have no idea how much vertical space to reserve. When the image loads, the page can shift.

However with:

<Image
  src="/product.jpg"
  width={800}
  height={600}
  alt="Product"
/>

the browser knows the aspect ratio ahead of time.

 

Importing Local Images

Next.js allows you to import images directly like any other component:

For example:

import Image from "next/image";
import productImage from "@/public/images/product.jpg";

export default function Product() {
  return (
    <Image
      src={productImage}
      alt="Product"
    />
  );
}

In this case Next.js can determine image metadata such as:

  • width
  • height
  • source

So you don’t need to manually specify them:

For example:

<Image
  src={productImage}
  alt="Product"
/>

Is often preferable to:

<Image
  src={productImage}
  width={800}
  height={600}
  alt="Product"
/>

when using statically imported local images.

 

Responsive Images

One of the most important features of the Image component is responsive image delivery.

Consider a product card:

<div className="product-card">
  <Image
    src="/images/product.jpg"
    width={500}
    height={500}
    alt="Product"
  />
</div>

Suppose that the image is actually displayed at:

  • 200px on mobile
  • 300px on tablet
  • 400px on desktop

Sending a 2000px-wide image to every device is wasteful.

Instead, you can use sizes prop:

<Image
  src="/images/product.jpg"
  width={800}
  height={800}
  sizes="(max-width: 768px) 50vw, 25vw"
  alt="Product"
/>

The sizes prop tells the browser approximately how much viewport width the image occupies.

For example:

Mobile: 50vw
Desktop: 25vw

This allows the browser to select an appropriately sized image.

 

How Image Sizes Work

Consider:

<Image
  src="/images/product.jpg"
  width={1200}
  height={800}
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  alt="Product"
/>

This means:

Viewport <= 768px
    → image occupies approximately 100% viewport width

Viewport <= 1200px
    → image occupies approximately 50% viewport width

Larger screens
    → image occupies approximately 33% viewport width

Which is very useful for things such as:

  • product grids
  • blog cards
  • galleries
  • listing pages

 

Using the fill property

Sometimes you don’t know the image’s exact dimensions.

For example, you may have a card:

<div className="card">
  <Image
    src="/images/property.jpg"
    fill
    alt="Property"
  />
</div>

The parent needs a defined positioning context:

.card {
  position: relative;
  width: 100%;
  height: 300px;
}

Now the image fills the parents.

Also you can use css object-fit with the fill property:

<Image
  src="/images/property.jpg"
  fill
  alt="Property"
  style={{
    objectFit: "cover",
  }}
/>

Or with css:

.image {
  object-fit: cover;
}

Then:

<Image
  src="/images/property.jpg"
  fill
  className="image"
  alt="Property"
/>

cover means the image fills the container while maintaining its aspect ratio. Some parts of the image may be cropped.

While if we use object-fit: contain: For items where the entire object must remain visible:

<Image
  src="/images/laptop.png"
  fill
  alt="Laptop"
  style={{
    objectFit: "contain",
  }}
/>

This is useful for:

  • product images
  • logos
  • icons
  • transparent PNGs

Unlike cover, contain doesn’t crop the image.

 

Lazy Loading Images

Images outside the initial viewport can generally be lazy-loaded.

For example:

<Image
  src="/images/product.jpg"
  width={600}
  height={600}
  alt="Product"
  loading="lazy"
/>

The browser doesn’t need to download every image immediately. With the loading prop set it to lazy. The default value is eager which means load the image immediately regardless if it’s in the viewport of not.

Image a product listing with 100 product:

Product 1
Product 2
Product 3
...
Product 100

If only the first few are visible, loading all 100 images immediately would waste bandwidth.

Lazy loading allows images further down the page to load when they’re needed.

The Image component already provides sensible lazy-loading behavior by default, so you usually don’t need to specify it manually.

 

Don’t Preload Everything

A common mistake is doing this:

<Image
  src="/product-1.jpg"
  preload
  ...
/>

<Image
  src="/product-2.jpg"
  preload
  ...
/>

<Image
  src="/product-3.jpg"
  preload
  ...
/>

This defeats the purpose of prioritization.

Instead:

<Image
  src="/hero.jpg"
  preload
  ...
/>

and allow normal images to load lazily.

Think of image priority as a limited resource.

 

Remote Images

Suppose your backend returns:

https://cdn.example.com/products/iphone.jpg

You cannot simply use any arbitrary remote URL without configuring it.

You might write:

<Image
  src="https://cdn.example.com/products/iphone.jpg"
  width={800}
  height={800}
  alt="iPhone"
/>

but the remote host needs to be allowed in your Next.js configuration.

Configuring Remote Images

In next.config.js, configure the remote image host.

For example:

const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "cdn.example.com",
        pathname: "/products/**",
      },
    ],
  },
};

export default nextConfig;

Now the remote image will load.

The advantage of remotePatterns is that you can restrict exactly which remote URLs are allowed instead of allowing an entire arbitrary domain.

 

Example: loading images from a Laravel based backend:

Suppose Laravel api return:

{
  "id": 10,
  "name": "Apartment",
  "image": "https://api.example.com/storage/properties/apartment.jpg"
}

Your Next.js component can do:

<Image
  src={property.image}
  width={800}
  height={600}
  alt={property.name}
/>

And configure:

const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "api.example.com",
        pathname: "/storage/properties/**",
      },
    ],
  },
};

export default nextConfig;

Another Example: loading images from a CDN:

If your images stored in a CDN like this:

https://cdn.example.com/images/...

you can configure that CDN as a remote image source.

And the remotePatterns will be:

const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "cdn.example.com",
        pathname: "/images/**",
      },
    ],
  },
};

export default nextConfig;

Then:

<Image
  src={imageUrl}
  width={1200}
  height={800}
  alt="Property"
/>

 

Image Quality

You may specify an image quality value:

<Image
  src="/images/property.jpg"
  width={1200}
  height={800}
  quality={75}
  alt="Property"
/>

Higher quality usually means:

Better visual quality
       ↓
Larger file
       ↓
More bandwidth

Use higher values only when needed.

 

Webp and AVIF

Modern image formats can significantly reduce file sizes compared with traditional JPEG and PNG.

You can configure the formats Next.js should use:

const nextConfig = {
  images: {
    formats: ["image/avif", "image/webp"],
  },
};

export default nextConfig;

The browser’s supported format is considered when serving the optimized image.

WebP is also widely supported and remains a strong general-purpose choice.

 

Images inside Server Components

One of the nice things about Next.js is that Image works naturally inside Server Components.

For example:

import Image from "next/image";

export default async function ProductPage() {
  const product = await getProduct();

  return (
    <div>
      <Image
        src={product.image}
        width={800}
        height={800}
        alt={product.name}
      />

      <h1>{product.name}</h1>
    </div>
  );
}

You don’t need to make the component a Client Component just because you’re rendering an image.

 

A practical Optimization Strategy

For most applications, you can follow this strategy:

Hero Image

<Image
  src="/hero.jpg"
  fill
  preload
  sizes="100vw"
  alt="..."
  style={{ objectFit: "cover" }}
/>

Product/listing card

<Image
  src={image}
  fill
  sizes="(max-width: 768px) 100vw, 33vw"
  alt={title}
  style={{ objectFit: "cover" }}
/>

Product Image

<Image
  src={image}
  fill
  sizes="(max-width: 768px) 50vw, 25vw"
  alt={name}
  style={{ objectFit: "contain" }}
/>

Content Image

<Image
  src={image}
  width={1200}
  height={800}
  sizes="(max-width: 768px) 100vw, 800px"
  alt="..."
/>

 

Example: Property Card:

import Image from "next/image";

export default function PropertyCard({ property }) {
  return (
    <article className="property-card">
      <div className="property-image">
        <Image
          src={property.image}
          alt={property.title}
          fill
          sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
          style={{
            objectFit: "cover",
          }}
        />
      </div>

      <div className="property-content">
        <h2>{property.title}</h2>
        <p>{property.location}</p>
        <strong>{property.price}</strong>
      </div>
    </article>
  );
}

CSS:

.property-card {
  overflow: hidden;
  border-radius: 12px;
}

.property-image {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
}

.property-content {
  padding: 16px;
}

This gives you:

  • responsive sizing
  • preserved aspect ratio
  • optimized image delivery
  • lazy loading behavior
  • correct cropping
  • appropriate image sizes for different screens

 

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted