DEV Community

sweet
sweet

Posted on

Image Optimization for Modern Web Applications: Formats, CDNs, Automation

Images account for 50-70% of a typical web page's weight. Optimizing them properly — choosing the right format, generating responsive sizes, automating compression, and serving from a CDN — is the single highest-impact performance optimization available. This guide covers the complete image optimization pipeline used at tanstackship.com, from development workflow to production delivery.


Image Format Comparison

Format Compression Transparency Animation Browser Support File Size vs PNG
JPEG Lossy 100% 50-70% smaller
PNG Lossless 100% Baseline
WebP Lossy + Lossless 97% 25-35% smaller
AVIF Lossy + Lossless 93% 50-60% smaller
JPEG XL Lossy + Lossless Growing 40-50% smaller

Recommendation in 2026: Serve AVIF as the primary format with WebP fallback. Most modern browsers (93%) support AVIF, and WebP covers the remaining.


Generating Optimized Images

Automated Build Pipeline

// scripts/optimize-images.ts
// Run during build process
import sharp from "sharp"
import { readdir, readFile, writeFile } from "fs/promises"
import path from "path"

interface ImageConfig {
  source: string       // Original image path
  outputDir: string    // Base output directory
  formats: string[]    // ['avif', 'webp', 'jpeg']
  sizes: number[]      // [640, 1200, 1920]
  quality: number      // 80
}

async function optimizeImage(config: ImageConfig) {
  const inputBuffer = await readFile(config.source)
  const filename = path.parse(config.source).name

  for (const format of config.formats) {
    for (const width of config.sizes) {
      const outputPath = path.join(
        config.outputDir,
        format,
        `${filename}-${width}w.${format}`
      )

      await sharp(inputBuffer)
        .resize(width, undefined, { withoutEnlargement: true })
        .toFormat(format as any, { quality: config.quality })
        .toFile(outputPath)

      console.log(`Created: ${outputPath}`)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Responsive Images with srcset

// components/OptimizedImage.tsx
interface OptimizedImageProps {
  src: string  // e.g., "/images/dashboard"
  alt: string
  widths?: number[]
  sizes?: string
  priority?: boolean
}

function OptimizedImage({
  src,
  alt,
  widths = [640, 1200, 1920],
  sizes = "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw",
  priority = false,
}: OptimizedImageProps) {
  const formats = ["avif", "webp", "jpeg"]
  const suffix = priority ? "fetchPriority='high'" : "loading='lazy'"

  return (
    <picture>
      {/* Modern format first */}
      {formats.map((format) => (
        <source
          key={format}
          type={`image/${format === "jpeg" ? "jpeg" : format}`}
          srcSet={widths
            .map((w) => `${src}-${w}w.${format} ${w}w`)
            .join(", ")
          }
          sizes={sizes}
        />
      ))}
      {/* Fallback */}
      <img
        src={`${src}-1200w.jpeg`}
        srcSet={widths
          .map((w) => `${src}-${w}w.jpeg ${w}w`)
          .join(", ")
        }
        alt={alt}
        width={1200}
        height={675}
        decoding="async"
        {...(priority ? { fetchPriority: "high" as const } : { loading: "lazy" as const })}
      />
    </picture>
  )
}
Enter fullscreen mode Exit fullscreen mode

CDN-Based Image Optimization

Using Cloudflare Image Resizing eliminates the need for build-time generation:

// Skip build-time optimization — transform on the fly
function CdnImage({ src, width, alt, priority = false }: CdnImageProps) {
  const baseUrl = "https://tanstackship.com/cdn-cgi/image"

  // Generate transform URLs
  const formats = [
    { format: "avif", url: `${baseUrl}/width=${width},format=avif,quality=80/${src}` },
    { format: "webp", url: `${baseUrl}/width=${width},format=webp,quality=80/${src}` },
    { format: "jpeg", url: `${baseUrl}/width=${width},format=jpeg,quality=85/${src}` },
  ]

  return (
    <picture>
      {formats.map(({ format, url }) => (
        <source key={format} type={`image/${format}`} srcSet={url} />
      ))}
      <img
        src={`${baseUrl}/width=${width},format=jpeg,quality=85/${src}`}
        alt={alt}
        width={width}
        loading={priority ? "eager" : "lazy"}
        fetchPriority={priority ? "high" : "auto"}
      />
    </picture>
  )
}
Enter fullscreen mode Exit fullscreen mode

Image Resizing Options

// Available CDN parameters — build helper
interface ImageTransformOptions {
  width?: number       // 1-4096
  height?: number      // 1-4096
  fit?: "scale-down" | "contain" | "cover" | "crop" | "pad"
  quality?: number     // 1-100
  format?: "webp" | "avif" | "jpeg" | "png"
  sharpen?: number     // 0-10
  blur?: number        // 0-250
  brightness?: number  // -100 to 100
  contrast?: number    // -100 to 100
  gamma?: number       // 0.1-9.99
}

function buildImageUrl(basePath: string, opts: ImageTransformOptions): string {
  const params = Object.entries(opts)
    .filter(([, v]) => v !== undefined)
    .map(([k, v]) => `${k}=${v}`)
    .join(",")

  return `/cdn-cgi/image/${params}/${basePath}`
}
Enter fullscreen mode Exit fullscreen mode

Lazy Loading Strategy

// Intelligent lazy loading based on viewport position
function LazyImage({ src, alt, threshold = 0.1 }: LazyImageProps) {
  const imgRef = useRef<HTMLImageElement>(null)
  const [loaded, setLoaded] = useState(false)
  const [inView, setInView] = useState(false)

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setInView(true)
          observer.disconnect()
        }
      },
      { rootMargin: "200px", threshold }
    )

    if (imgRef.current) observer.observe(imgRef.current)
    return () => observer.disconnect()
  }, [threshold])

  return (
    <div ref={imgRef} className="image-wrapper" style={{ minHeight: "200px" }}>
      {inView ? (
        <img
          src={src}
          alt={alt}
          onLoad={() => setLoaded(true)}
          className={loaded ? "loaded" : "loading"}
          loading="lazy"
        />
      ) : (
        <div className="image-skeleton" />
      )}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode
Lazy Loading Method Pros Cons
Native loading="lazy" Zero JS, browser-native Less control over threshold
Intersection Observer Precise control Requires JS
Below-fold only Simple Manual per-image
Priority hints Works with native lazy Limited browser support

Automated Image Pipeline

// server/images/pipeline.ts
export const uploadImage = createServerFn({ method: "POST" }).handler(
  async ({ request, context }) => {
    const formData = await request.formData()
    const file = formData.get("image") as File

    // Validation
    if (!file.type.startsWith("image/")) throw new Error("Invalid file type")
    if (file.size > 10 * 1024 * 1024) throw new Error("File too large")

    const buffer = await file.arrayBuffer()
    const id = crypto.randomUUID()
    const key = `images/${id}`

    // Store original
    await context.env.MY_BUCKET.put(`${key}/original`, buffer, {
      httpMetadata: { contentType: file.type },
    })

    // Generate thumbnails via Image Resizing
    const sizes = [
      { width: 150, suffix: "thumb" },
      { width: 400, suffix: "small" },
      { width: 800, suffix: "medium" },
      { width: 1600, suffix: "large" },
    ]

    for (const { width, suffix } of sizes) {
      // Store reference — actual resize happens on CDN
      await context.env.MY_BUCKET.put(`${key}/${suffix}.webp`, buffer)
    }

    return {
      id,
      urls: {
        original: `https://images.tanstackship.com/${key}/original`,
        thumb: `https://images.tanstackship.com/cdn-cgi/image/width=150/${key}/original`,
        small: `https://images.tanstackship.com/cdn-cgi/image/width=400/${key}/original`,
        medium: `https://images.tanstackship.com/cdn-cgi/image/width=800/${key}/original`,
        large: `https://images.tanstackship.com/cdn-cgi/image/width=1600/${key}/original`,
      },
    }
  }
)
Enter fullscreen mode Exit fullscreen mode

Performance Impact

Image Type Before After (AVIF + Responsive) Savings
Hero image (1920px) 1.2 MB (JPEG) 180 KB (AVIF) 85%
Blog thumbnail (400px) 180 KB (JPEG) 35 KB (AVIF) 81%
Product screenshot (1200px) 850 KB (PNG) 120 KB (WebP) 86%
Icon set (32px SVG) 25 KB 25 KB (already optimized)
Total page weight 2.5 MB 380 KB 85% reduction

Image CDN Cost Comparison

Provider Bandwidth Transformations Storage
Cloudflare Images Included with Workers $0.50/1000 unique $5/1000 images
Cloudflare Image Resizing Free with Pro Biz per-request N/A (via R2)
imgix $0.10/GB Included $0.20/GB
Cloudinary Free tier: 25GB CDN 25GB transformations 25GB storage
AWS CloudFront + Lambda@Edge $0.085/GB Lambda cost S3 pricing

Image Optimization Checklist

  • [ ] AVIF used as primary format, WebP as fallback
  • [ ] Responsive images with srcset and sizes attributes
  • [ ] Lazy loading for all images below the fold
  • [ ] Explicit width and height to prevent CLS
  • [ ] CDN-based image transformation for dynamic sizing
  • [ ] Automated build pipeline for static images
  • [ ] Image quality set to 80-85 (diminishing returns after this)
  • [ ] SVG used for icons and illustrations where possible
  • [ ] Preload LCP images with fetchpriority="high"
  • [ ] Monitor Lighthouse image optimization score weekly

Conclusion

Image optimization is not optional — it is the single highest-impact performance optimization for most web pages. The modern approach combines three strategies:

  1. Format selection: AVIF for quality, WebP for compatibility
  2. Responsive delivery: Different sizes for different viewports
  3. CDN optimization: Transform on the edge, cache at the edge

With Cloudflare Image Resizing, you can skip the build-time optimization pipeline entirely and let the CDN handle format selection, resizing, and compression dynamically. This simplifies the development workflow while delivering optimal images to every browser.

For a production site with complete image optimization, see tanstackship.com.

Related Resources

Top comments (0)