Optimizing Images with NextJS Image
Introduction
The NextJS Image component is a powerful solution that helps automatically optimize images in web applications. The Image component has been implemented by NextJS with many superior functions that support displaying images more effectively than a standard HTML image tag, such as
- Automatically resizing images to fit the device
- Supporting modern image formats (like WebP and AVIF)
- Preventing Cumulative Layout Shift (CLS) by holding space for images
- Integrating a built-in lazy loading mechanism to speed up initial page load speeds.
Detail
In this article, I will provide an example of loading a product list in ecommerce pages so you can see the advantages of NextJS Image in automatic image optimization.
Create file app/image/types.ts
export interface Product {
id: number
title: string
description: string
price: number
thumbnail: string
category: string
}
export interface ProductResponse {
products: Product[]
total: number
skip: number
limit: number
}
export interface Category {
id: number
name: string
img: string
}
Create file app/image/api.ts
import type {ProductResponse} from './types'
export async function getProducts(): Promise<ProductResponse> {
const res = await fetch('https://dummyjson.com/products?limit=8')
if (!res.ok) throw new Error('Failed to fetch')
return res.json()
}
export const CATEGORIES = [
{id: 1, name: 'Electronics', img: 'https://picsum.photos/200/200?random=1'},
{id: 2, name: 'Fashion', img: 'https://picsum.photos/200/200?random=2'},
{id: 3, name: 'Home', img: 'https://picsum.photos/200/200?random=3'},
{id: 4, name: 'Beauty', img: 'https://picsum.photos/200/200?random=4'},
]
Create file app/image/ProductList.tsx
import Image from 'next/image'
import type {Product} from './types'
interface ProductListProps {
products: Product[]
}
export default function ProductList({products}: ProductListProps) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{products.map(product => (
<div key={product.id} className="group border p-4">
<div className="relative aspect-square">
<Image
src={product.thumbnail}
alt={product.title}
fill
sizes="(max-width: 768px) 100vw, 25vw"
loading="lazy"
/>
</div>
<h3>{product.title}</h3>
<p>${product.price}</p>
</div>
))}
</div>
)
}
Create file app/image/page.tsx
import Image from 'next/image'
import {CATEGORIES, getProducts} from './api'
import ProductList from './ProductList'
export default async function HomePage() {
const data = await getProducts()
return (
<main className="max-w-7xl mx-auto px-4 py-8">
<section className="relative w-full h-[400px] mb-12 rounded-xl overflow-hidden">
<Image
src="https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe"
alt="E-commerce Banner"
fill
priority
sizes="100vw"
className="object-cover"
/>
<div className="absolute inset-0 bg-black/20 flex items-center justify-center">
<h1 className="text-white text-5xl font-bold">New Collection</h1>
</div>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold mb-6">Categories</h2>
<div className="flex gap-6 overflow-x-auto pb-4 scrollbar-hide">
{CATEGORIES.map(cat => (
<div key={cat.id} className="flex-shrink-0 text-center">
<div className="relative w-24 h-24 mb-2">
<Image
src={cat.img}
alt={cat.name}
fill
loading="eager"
sizes="96px"
className="rounded-full object-cover border-2 border-gray-100"
/>
</div>
<span className="text-sm font-medium">{cat.name}</span>
</div>
))}
</div>
</section>
<section>
<h2 className="text-2xl font-bold mb-6">Featured Products</h2>
<ProductList products={data.products} />
</section>
</main>
)
}
The image loading priority order includes
- priority: Prioritize immediate loading, only applied to a few of the most important above-the-fold images that will be shown directly to the user as soon as they access the site, this is the most important main factor that helps improve LCP (Largest Contentful Paint) metrics
- loading=eager: to ensure these images are always ready when the user first enters the page.
- loading=lazy: the default value of Image, helping the browser save bandwidth, it can be used for most images on the page to only load images when the user scrolls to them.
Distinguishing width, height and sizes
- width, height: the size of the placeholder space the browser will reserve to load the image into
- sizes: this is the factor to identify conditions and load images corresponding to device size
As in the code above, you see I used
sizes="(max-width: 768px) 100vw, 25vw": NextJS will create corresponding resized images
- if the screen size is 768px or less like mobile then take an image almost as large as the mobile screen
- if the screen size is larger than 768px for desktop, at this point it will divide into 4 columns so only take an image as small as 1/4 of the desktop screen
sizes="96px": NextJS will create small resized images (like 128px, 256px) instead of large images for Desktop, the browser will choose whichever image is closest to 96px to load.
import type {NextConfig} from 'next'const nextConfig: NextConfig = {images: {remotePatterns: [{protocol: 'https',hostname: '**.dummyjson.com',},{protocol: 'https',hostname: 'images.unsplash.com',},{protocol: 'https',hostname: 'picsum.photos',},],},}export default nextConfig
To make image resizing work, you need to rebuild the project and start in production mode, after viewing the pages as desktop and mobile you should access the .next/cache/images folder where automatically resized image files corresponding to different sizes will appear.
Happy coding!
Comments
Post a Comment