All Features / Image Optimization System
3-5 hours
intermediate

Image Optimization System

Thumbnails, compression, lazy loading, responsive images

imagesperformanceseo

Paste this into Claude Code to start implementing

šŸ”§Works With

This spec is compatible with:

The implementation prompt includes guidance for these tech stacks.

Overview

Production-grade image optimization pipeline: automatic resizing, format conversion (WebP/AVIF), thumbnail generation, lazy loading, and responsive images. Reduces page load by 60-80% while maintaining visual quality.

Use Cases

  • E-commerce: Product photos with multiple sizes
  • Social platforms: User avatars, cover images, photo feeds
  • Blogs/CMS: Article images with responsive breakpoints
  • Marketing sites: Hero images, testimonials, team photos

When to Use This Pattern

Use this pattern when you need to:

  • Serve images at optimal sizes for different devices
  • Reduce bandwidth and improve page load speed
  • Generate thumbnails automatically on upload
  • Support modern formats (WebP, AVIF) with fallbacks
  • Implement lazy loading for below-the-fold images
  • Deliver images via CDN for global performance

Pro Tips

Before you start implementing, read these carefully:

  1. Next.js Image component handles 90% automatically - Use it unless you need custom logic
  2. Generate thumbnails on upload, not on request - Faster UX, no waiting
  3. Always provide width/height - Prevents layout shift, improves Core Web Vitals
  4. Use WebP with JPEG fallback - 30% smaller, 95% browser support
  5. Lazy load below-the-fold images - Saves bandwidth, faster initial load

Implementation Phases

Phase 1: Next.js Image Component (Easiest)

Use Next.js built-in optimization:

import Image from 'next/image'

export default function ProductImage({ src, alt }: { src: string; alt: string }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={800}
      height={600}
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      quality={85}
      loading="lazy"
      className="rounded-lg"
    />
  )
}

What Next.js Image does automatically:

  • āœ… Resizes to optimal size for device
  • āœ… Converts to WebP/AVIF (modern formats)
  • āœ… Lazy loads by default
  • āœ… Prevents layout shift
  • āœ… Serves via CDN (Vercel)
  • āœ… Blur-up placeholder

Configure in next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.supabase.co',
      },
      {
        protocol: 'https',
        hostname: '**.cloudinary.com',
      },
    ],
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
}

module.exports = nextConfig

Phase 2: Server-Side Thumbnail Generation

Generate thumbnails on upload with Sharp:

npm install sharp
// app/api/upload/route.ts
import sharp from 'sharp'
import { put } from '@vercel/blob'

export async function POST(request: Request) {
  const formData = await request.formData()
  const file = formData.get('file') as File
  const buffer = Buffer.from(await file.arrayBuffer())

  // Generate multiple sizes
  const thumbnail = await sharp(buffer)
    .resize(150, 150, { fit: 'cover' })
    .webp({ quality: 80 })
    .toBuffer()

  const medium = await sharp(buffer)
    .resize(800, 800, { fit: 'inside' })
    .webp({ quality: 85 })
    .toBuffer()

  const large = await sharp(buffer)
    .resize(1920, 1920, { fit: 'inside' })
    .webp({ quality: 90 })
    .toBuffer()

  // Upload all sizes
  const [thumbnailBlob, mediumBlob, largeBlob] = await Promise.all([
    put(`thumbnails/${file.name}`, thumbnail, { access: 'public' }),
    put(`medium/${file.name}`, medium, { access: 'public' }),
    put(`large/${file.name}`, large, { access: 'public' }),
  ])

  // Save URLs to database
  await prisma.image.create({
    data: {
      original: largeBlob.url,
      medium: mediumBlob.url,
      thumbnail: thumbnailBlob.url,
      userId: session.user.id,
    },
  })

  return NextResponse.json({
    thumbnail: thumbnailBlob.url,
    medium: mediumBlob.url,
    large: largeBlob.url,
  })
}

Phase 3: Responsive Images Component

Build reusable image component:

'use client'

import { useState } from 'react'
import Image from 'next/image'

interface ResponsiveImageProps {
  src: string
  alt: string
  thumbnail?: string
  width: number
  height: number
  priority?: boolean
}

export default function ResponsiveImage({
  src,
  alt,
  thumbnail,
  width,
  height,
  priority = false
}: ResponsiveImageProps) {
  const [isLoading, setIsLoading] = useState(true)

  return (
    <div className="relative overflow-hidden rounded-lg bg-stone-100">
      {/* Blur placeholder (thumbnail) */}
      {thumbnail && isLoading && (
        <Image
          src={thumbnail}
          alt=""
          fill
          className="blur-sm"
        />
      )}

      {/* Full image */}
      <Image
        src={src}
        alt={alt}
        width={width}
        height={height}
        loading={priority ? 'eager' : 'lazy'}
        priority={priority}
        onLoadingComplete={() => setIsLoading(false)}
        className={`
          transition-opacity duration-300
          ${isLoading ? 'opacity-0' : 'opacity-100'}
        `}
      />
    </div>
  )
}

Phase 4: CDN Integration

Option A: Cloudinary (Best for complex transformations)

// lib/cloudinary.ts
export function getCloudinaryUrl(
  publicId: string,
  options: {
    width?: number
    height?: number
    quality?: number
    format?: 'auto' | 'webp' | 'avif'
  } = {}
) {
  const { width, height, quality = 'auto', format = 'auto' } = options

  const transformations = [
    width && `w_${width}`,
    height && `h_${height}`,
    `q_${quality}`,
    `f_${format}`,
    'c_limit', // Don't upscale
  ].filter(Boolean).join(',')

  return `https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/upload/${transformations}/${publicId}`
}

// Usage
<Image
  src={getCloudinaryUrl('user-avatar', { width: 150, height: 150, format: 'webp' })}
  alt="User avatar"
  width={150}
  height={150}
/>

Option B: Imgix (Best for real-time transformations)

// lib/imgix.ts
export function getImgixUrl(
  path: string,
  options: {
    width?: number
    height?: number
    quality?: number
    format?: 'auto' | 'webp' | 'avif'
  } = {}
) {
  const { width, height, quality = 75, format = 'auto' } = options

  const params = new URLSearchParams({
    ...(width && { w: width.toString() }),
    ...(height && { h: height.toString() }),
    q: quality.toString(),
    fm: format,
    fit: 'crop',
    auto: 'format,compress',
  })

  return `https://${process.env.IMGIX_DOMAIN}/${path}?${params}`
}

Edge Cases to Handle

Layout shift (CLS):

  • Image loads, page jumps
  • Solution: Always provide width/height, use aspect-ratio CSS

Broken image links:

  • Image deleted from storage but URL in database
  • Solution: Fallback image, error boundary
<Image
  src={src}
  alt={alt}
  width={800}
  height={600}
  onError={(e) => {
    e.currentTarget.src = '/placeholder.png'
  }}
/>

Large images on mobile:

  • Serving 4K image to phone
  • Solution: Responsive sizes attribute
sizes="(max-width: 768px) 100vw, 50vw"

Tech Stack Recommendations

Minimum Viable Stack

  • Framework: Next.js with Image component
  • Storage: Vercel Blob or Cloudinary
  • No additional packages needed

Production-Grade Stack

  • Framework: Next.js 15 with Image component
  • Processing: Sharp for server-side resizing
  • CDN: Cloudinary or Imgix for transformations
  • Storage: S3 + CloudFront or Vercel Blob
  • Monitoring: Cloudflare Image Analytics

Full Implementation Prompt

Copy this prompt to use with Claude Code:


I need to implement image optimization with thumbnails, lazy loading, and responsive images. Before we start, help me review my setup.

First, let's understand my use case:

  1. How many images does my app handle? (Dozens, hundreds, thousands per day?)
  2. Are images user-uploaded or admin-curated?
  3. Do I need real-time transformations or can I pre-generate sizes?
  4. Am I using Next.js? (Makes optimization much easier)
  5. Do I already have a CDN or image service? (Cloudinary, Imgix, Vercel)

Then we'll implement: Phase 1: Configure Next.js Image component (if using Next.js) Phase 2: Set up Sharp for thumbnail generation on upload Phase 3: Build responsive image component with blur placeholders Phase 4: Configure CDN for global delivery Phase 5: Implement lazy loading for performance

After implementation, test:

  • Images load at correct sizes for mobile/desktop
  • WebP format served to modern browsers
  • Lazy loading works (check Network tab)
  • No layout shift (check Lighthouse CLS score)
  • Thumbnails generated on upload

Sound good? Let's start with your image optimization setup.


Related Feature Specs

Success Criteria

āœ… Images load at optimal sizes for each device āœ… Modern formats (WebP/AVIF) served automatically āœ… Thumbnails generated on upload āœ… Lazy loading below-the-fold images āœ… No layout shift (CLS < 0.1) āœ… Fast load times (<2s LCP)

Common Mistakes to Avoid

āŒ Not providing width/height (causes layout shift) āŒ Loading full-size images on mobile āŒ Not using lazy loading āŒ Serving PNG/JPEG only (no WebP) āŒ Client-side resizing (slow, uses data) āŒ Not using CDN (slow for global users)


Last Updated: 2025-12-04 Difficulty: Intermediate Estimated Time: 3-5 hours Prerequisites: File upload basics, Next.js Image component knowledge

Need Implementation Help?

Get expert guidance on architecture, security, and best practices.

Book a Consultation
Image Optimization System | Claude Code Implementation Guide | HashBuilds