Image Optimization
Image optimization with next/image, asset handling, fonts, and performance tuning for Next.js applications
You are an expert in optimizing images and static assets in Next.js applications using the built-in `next/image` component and related techniques.
## Key Points
- Serves images in modern formats (WebP, AVIF) based on browser support
- Resizes images on-demand and caches them
- Lazy-loads images by default (only loads when entering the viewport)
- Prevents Cumulative Layout Shift (CLS) by requiring dimensions
1. **Local images**: Imported from the filesystem; dimensions are known at build time.
2. **Remote images**: Loaded from external URLs; dimensions must be provided or `fill` must be used.
- Always use the `<Image>` component instead of raw `<img>` tags for automatic optimization.
- Set `priority` on the Largest Contentful Paint (LCP) image (hero image, above-the-fold banner).
- Provide accurate `sizes` for responsive and fill images — incorrect values lead to oversized downloads.
- Use `placeholder="blur"` for local images to improve perceived performance.
- Enable AVIF format (`formats: ["image/avif", "image/webp"]`) for the best compression ratios.
- Use `next/font` for all text fonts to eliminate font-related layout shift and external network requests.
## Quick Example
```tsx
export const metadata: Metadata = {
openGraph: {
images: ["/api/og?title=My+Page"],
},
};
```skilldb get nextjs-skills/Image OptimizationFull skill: 318 linesImage Optimization — Next.js
You are an expert in optimizing images and static assets in Next.js applications using the built-in next/image component and related techniques.
Overview
Next.js provides automatic image optimization through the <Image> component from next/image. It handles lazy loading, responsive sizing, format conversion (WebP/AVIF), and caching out of the box. Combined with next/font and static asset best practices, you can achieve excellent Core Web Vitals scores.
Core Concepts
The <Image> Component
The <Image> component wraps the native <img> tag with automatic optimization:
- Serves images in modern formats (WebP, AVIF) based on browser support
- Resizes images on-demand and caches them
- Lazy-loads images by default (only loads when entering the viewport)
- Prevents Cumulative Layout Shift (CLS) by requiring dimensions
Image Sources
- Local images: Imported from the filesystem; dimensions are known at build time.
- Remote images: Loaded from external URLs; dimensions must be provided or
fillmust be used.
Core Philosophy
Image optimization in Next.js is built on the principle that performance should be automatic, not manual. The <Image> component handles format negotiation, responsive sizing, lazy loading, and cache management behind a single declarative API. Developers should not need to manually create WebP variants, write srcset attributes, or implement intersection observers — the framework does it for you when you use the right component.
The performance gains from proper image handling are disproportionately large. Images are typically the heaviest assets on a page, and they directly impact Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), and total page weight. Getting images right often improves Core Web Vitals more than any amount of JavaScript optimization. This is why Next.js makes the optimized path the default path: using <Image> instead of <img> is the single highest-leverage performance decision in most applications.
Font optimization follows the same philosophy of eliminating unnecessary network requests and layout instability. next/font downloads fonts at build time, self-hosts them from your domain, and applies size-adjust to prevent layout shift. The result is zero-CLS text rendering with no external font service dependency, which is both faster and more privacy-respecting than loading from Google Fonts at runtime.
Implementation Patterns
Local Images
import Image from "next/image";
import heroImage from "@/public/images/hero.jpg";
export default function Hero() {
return (
<Image
src={heroImage}
alt="Product showcase"
placeholder="blur" // Automatic blur-up placeholder
priority // Preload for above-the-fold images
className="rounded-lg"
/>
);
}
With local imports, width, height, and blurDataURL are inferred automatically.
Remote Images
import Image from "next/image";
export default function UserAvatar({ user }: { user: User }) {
return (
<Image
src={user.avatarUrl}
alt={`${user.name}'s avatar`}
width={80}
height={80}
className="rounded-full"
/>
);
}
Configure allowed remote domains in next.config.ts:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.example.com",
pathname: "/images/**",
},
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
],
},
};
export default nextConfig;
Fill Mode (Unknown Dimensions)
When you do not know the image dimensions ahead of time, use fill to stretch the image to its parent container:
export default function Card({ image }: { image: string }) {
return (
<div className="relative aspect-video w-full">
<Image
src={image}
alt="Card image"
fill
className="object-cover rounded-md"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
);
}
Important: The parent must have
position: relativeand defined dimensions. Thesizesattribute is critical forfillimages to generate correct responsivesrcset.
Responsive Images with sizes
The sizes attribute tells the browser how wide the image will be at different viewport widths so it can pick the right source from the srcset:
<Image
src="/photos/landscape.jpg"
alt="Landscape"
width={1200}
height={800}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px"
/>
Image Configuration Options
// next.config.ts
const nextConfig: NextConfig = {
images: {
// Supported formats (order matters — first supported wins)
formats: ["image/avif", "image/webp"],
// Custom device breakpoints for srcset
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// Cache duration in seconds (default: 60)
minimumCacheTTL: 86400, // 24 hours
// Use a CDN loader for external optimization
// loader: "custom",
// loaderFile: "./lib/image-loader.ts",
},
};
Custom Image Loader
For external image CDNs like Cloudinary, Imgix, or Cloudflare Images:
// lib/image-loader.ts
export default function cloudinaryLoader({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) {
const params = [
`f_auto`,
`c_limit`,
`w_${width}`,
`q_${quality || "auto"}`,
];
return `https://res.cloudinary.com/demo/image/upload/${params.join(",")}/${src}`;
}
// next.config.ts
const nextConfig: NextConfig = {
images: {
loader: "custom",
loaderFile: "./lib/image-loader.ts",
},
};
Font Optimization with next/font
// app/layout.tsx
import { Inter, JetBrains_Mono } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
});
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
display: "swap",
variable: "--font-mono",
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${inter.variable} ${jetbrainsMono.variable}`}>
<body className="font-sans">{children}</body>
</html>
);
}
next/font downloads fonts at build time and self-hosts them with zero layout shift.
Local Font Files
import localFont from "next/font/local";
const calSans = localFont({
src: "./fonts/CalSans-SemiBold.woff2",
display: "swap",
variable: "--font-cal",
});
Open Graph Images
Generate dynamic OG images using next/og:
// app/api/og/route.tsx
import { ImageResponse } from "next/og";
export const runtime = "edge";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get("title") ?? "My Site";
return new ImageResponse(
(
<div
style={{
display: "flex",
fontSize: 60,
background: "linear-gradient(to bottom, #1e293b, #0f172a)",
color: "white",
width: "100%",
height: "100%",
alignItems: "center",
justifyContent: "center",
padding: 48,
}}
>
{title}
</div>
),
{ width: 1200, height: 630 }
);
}
Reference it in metadata:
export const metadata: Metadata = {
openGraph: {
images: ["/api/og?title=My+Page"],
},
};
Best Practices
- Always use the
<Image>component instead of raw<img>tags for automatic optimization. - Set
priorityon the Largest Contentful Paint (LCP) image (hero image, above-the-fold banner). - Provide accurate
sizesfor responsive and fill images — incorrect values lead to oversized downloads. - Use
placeholder="blur"for local images to improve perceived performance. - Enable AVIF format (
formats: ["image/avif", "image/webp"]) for the best compression ratios. - Use
next/fontfor all text fonts to eliminate font-related layout shift and external network requests. - Self-host critical static assets in
public/rather than loading them from third-party CDNs.
Common Pitfalls
- Missing
sizeswithfill: Withoutsizes, the browser may download the full-resolution image even on mobile. Always specifysizeswhen usingfill. - Not configuring
remotePatterns: Remote images fail without allowlisting the domain innext.config.ts. UseremotePatterns(not the deprecateddomainsarray). - Forgetting
priorityon LCP images: Above-the-fold images are lazy-loaded by default, hurting LCP. Addpriorityto disable lazy loading for the hero image. - CLS from missing dimensions: If you omit
width/heightand do not usefill, the layout shifts when the image loads. Always specify dimensions or usefillwith a sized parent. - Over-optimizing: Do not convert tiny icons or logos through the image optimizer. For small SVGs, import them directly as React components or use inline SVG.
Anti-Patterns
-
Using raw
<img>tags instead of<Image>fromnext/image. Raw<img>tags bypass all automatic optimization — no lazy loading, no responsive sizing, no format conversion, no CLS prevention. Every image in a Next.js application should go through the<Image>component unless it is a tiny inline SVG. -
Omitting the
sizesattribute on responsive or fill images. Withoutsizes, the browser has no way to choose the right source from thesrcsetand defaults to the full-resolution image. This means mobile users download desktop-sized images, wasting bandwidth and slowing LCP. Always provide accuratesizesbased on your layout breakpoints. -
Loading hero images without the
priorityprop. Above-the-fold images are lazy-loaded by default, which means the browser waits until the image element enters the viewport before requesting it. For LCP images like hero banners and product photos, this delay is entirely unnecessary. Addpriorityto preload them. -
Configuring
remotePatternswith overly broad wildcards. Settinghostname: "**"orpathname: "/**"allows any external domain to be optimized through your server, which can be abused for bandwidth amplification attacks. Allowlist only the specific domains and paths your application actually uses. -
Running images through the Next.js optimizer when a CDN loader would be better. If you already use Cloudinary, Imgix, or Cloudflare Images, the images are already optimized at the CDN edge. Running them through the Next.js optimizer adds latency and double-compresses them. Use a custom loader to generate CDN URLs directly.
Install this skill directly: skilldb add nextjs-skills
Related Skills
API Routes
Route Handlers for building REST APIs, handling webhooks, streaming responses, and CORS in Next.js App Router
App Router
App Router fundamentals including file-based routing, layouts, loading states, and parallel routes in Next.js
Authentication
Authentication patterns using NextAuth.js (Auth.js) and Clerk for protecting routes and managing sessions in Next.js
Data Fetching
Data fetching and caching strategies including Server Components, fetch options, ISR, and the Next.js cache layers
Deployment
Deployment strategies for Next.js including Vercel, self-hosting with Node.js, Docker containers, and static export
Middleware
Middleware patterns for request interception, redirects, rewrites, authentication guards, and geo-routing in Next.js