Images
Complete guide to optimizing web images - formats, sizes, loading strategies and Next.js best practices.
Overview
This playbook covers:
- Choosing the right image format for each use case
- Optimal sizes and resolutions (mobile/desktop)
- Loading and smooth display strategies
- Responsive images with srcset
- Next.js specific optimizations
Image Formats
AVIF
The most performant format currently available.
Pros:
- Best compression (30-50% lighter than WebP/JPEG)
- Modern color management
- Ideal for photos and complex visuals
Cons:
- Slower encoding at build time
- More CPU-intensive decoding
WebP
Excellent quality/size tradeoff.
Pros:
- Very compact (much better than PNG/JPEG)
- Fast decoding
- Excellent browser support
Cons:
- Slightly less efficient compression than AVIF
PNG
Reserved for UI elements only.
Pros:
- Perfect for logos, icons, sharp transparencies
- Lossless quality
Cons:
- Catastrophic for photos/backgrounds (huge files)
SVG
For vector elements only.
Pros:
- Tiny size for vector shapes
- Perfectly scalable
- Ideal for logos, patterns, flat illustrations
Cons:
- Not suitable for photos or complex renders
Base64
Avoid for real images.
Usage:
- Only for tiny placeholders (blur)
- Avoids extra requests for tiny assets
Cons:
- Bloats HTML/JS
- No dedicated HTTP cache
Production Recommendation
AVIF (primary) → WebP (fallback) → JPEG (legacy)
- Use AVIF as primary format with WebP fallback
- Keep PNG only for UI assets or logos with transparency
- Reserve base64 for very small placeholders only
Optimal Sizes
Desktop (backgrounds, hero images)
| Usage | Width | Height | Target Size |
|---|---|---|---|
| Half-screen (~800px visible) | 1200-1600px | 900-1000px | 150-350 KB |
| Full screen | 1920-2400px | 1080-1200px | 200-400 KB |
Mobile
| Usage | Width | Height | Target Size |
|---|---|---|---|
| Full screen portrait | 750-1080px | 1200-1600px | 80-200 KB |
| Header/Hero | 750-1080px | 600-800px | 50-150 KB |
Practical Examples
Desktop auth background : ~1600×1000 → 150-300 KB
Mobile auth background : ~900×1600 → 80-150 KB
Loading and Display
Priority vs Lazy Loading
// Critical page (login) - use priority
<Image
src="/auth-bg.avif"
priority
// ...
/>
// Secondary pages - lazy loading by default
<Image
src="/signup-bg.avif"
// no priority = lazy loading
/>Rule: Only one priority image per critical page.
Blur Placeholder + Fade-in
'use client'
import Image from 'next/image'
import { useState } from 'react'
export function BackgroundImage({ src, alt }: { src: string; alt: string }) {
const [isLoaded, setIsLoaded] = useState(false)
return (
<Image
src={src}
alt={alt}
fill
className={`object-cover transition-opacity duration-500 ${
isLoaded ? 'opacity-100' : 'opacity-0'
}`}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
onLoadingComplete={() => setIsLoaded(true)}
/>
)
}Compression
Recommended pipeline:
- Export from Figma/Photoshop as high quality JPEG (80-90)
- Convert to AVIF/WebP with quality 50-70
- Tools:
sharp,imagemin, Cloudinary, ImageKit
Target: Max 300 KB per desktop visual, less for mobile.
Responsive Images
Why use srcset?
- Avoids over-downloading on mobile
- Browser picks the right resolution
- Next.js handles it automatically via
sizes
Example with sizes
<Image
src="/auth-bg.avif"
alt="Background"
fill
className="object-cover"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
50vw
"
/>sizes tells the browser the actual width of the image at each breakpoint, allowing Next.js to serve the right resolution.
Next.js Image (App Router)
Basic Configuration
import Image from 'next/image'
// Background with fill
<div className="relative h-screen">
<Image
src="/hero-bg.avif"
alt="Hero background"
fill
priority
className="object-cover"
placeholder="blur"
sizes="100vw"
/>
</div>Remote Images (CDN)
// next.config.mjs
export default {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/images/**',
},
],
},
}Automatic Formats
With Vercel Image Optimization, provide a high quality source (JPEG) and Next.js will serve AVIF/WebP automatically based on browser support.
Complete Example: Auth Layout
// app/(auth)/layout.tsx
import Image from 'next/image'
export default function AuthLayout({
children
}: {
children: React.ReactNode
}) {
return (
<div className="grid min-h-screen grid-cols-1 lg:grid-cols-2">
{/* Desktop background */}
<div className="relative hidden lg:block">
<Image
src="/auth-bg-desktop.avif"
alt="Auth background"
fill
priority
className="object-cover"
placeholder="blur"
sizes="(max-width: 1024px) 0px, 50vw"
/>
</div>
{/* Content + Mobile background */}
<div className="relative flex items-center justify-center px-6 py-8">
<div className="absolute inset-0 lg:hidden -z-10">
<Image
src="/auth-bg-mobile.avif"
alt="Auth background mobile"
fill
className="object-cover"
placeholder="blur"
sizes="100vw"
/>
</div>
<div className="relative z-10 w-full max-w-md">
{children}
</div>
</div>
</div>
)
}Checklist
- AVIF/WebP format used (no PNG for photos)
- Sizes adapted to viewport (no 4K on mobile)
- Size < 300 KB desktop, < 200 KB mobile
-
priorityonly on critical page image -
sizesdefined for responsive images - Blur placeholder for smooth loading
- CSS fade-in for elegant transition