Building a Serverless Image Optimization Pipeline on Edge Functions in 2026

By Optimage

Edge Functions can process and serve optimized images from the network node closest to each user. This is the architecture behind sub-50ms image delivery at global scale.

Why Edge Image Processing Changes Everything

Traditional image optimization works like this: user requests an image, the request travels to your origin server (probably in a single data center), the origin processes and serves the image, the response travels back. Round trips of 80-200ms are common even for users on fast connections, purely from geographic latency.

Edge Functions invert this model. Instead of one origin processing all requests, you deploy a tiny function to 200+ network nodes distributed globally. The user's request hits a node that might be 5ms away instead of 150ms away. The function processes the image on-demand and caches the result at that edge node.

For image optimization specifically, this means: a user in Lagos gets an AVIF-optimized image from a node in Lagos. A user in Seoul gets the same image from a node in Seoul. Both get sub-50ms time-to-first-byte.

The Three Major Edge Platforms

Vercel Edge Functions are the most developer-friendly option for Next.js applications. They run on V8 isolates (the same runtime as Cloudflare Workers), deploy automatically with your Next.js build, and integrate with Vercel's edge network spanning 100+ regions. The limitation: no Node.js APIs, no file system, and the runtime is based on the Web APIs spec rather than Node.js.

Cloudflare Workers offer the broadest global network (300+ cities) and the most mature edge computing platform. Workers run on V8 isolates with Cloudflare's own KV storage and R2 object storage. No Cold Start latency is a significant advantage: Workers run in under 1ms vs Lambda's 100-200ms cold start penalty.

AWS Lambda@Edge runs full Node.js (with limitations) at 13 CloudFront edge locations. Less distributed than Vercel or Cloudflare but with full Node.js compatibility, which means Sharp can potentially run with some configuration effort.

What Edge Functions Cannot Do

Before designing your architecture, understand the hard constraints:

No native binaries. Edge Functions run in a V8 isolate, not a Linux container. Sharp, which depends on libvips compiled C code, cannot run on Vercel Edge or Cloudflare Workers. This rules out server-side Sharp-based processing at the edge.

Memory limits. Vercel Edge Functions have a 128MB memory limit. Cloudflare Workers default to 128MB, up to 2GB with Workers Unbound. Processing a high-resolution image in a 128MB budget requires careful streaming.

CPU time limits. Vercel Edge Functions cap at 25 seconds wall time. Cloudflare Workers get 30 seconds on Workers Paid. These are ample for image serving but not for batch processing or heavy transcoding.

No file system. Everything must be fetched from object storage (S3, R2, Cloudflare KV, etc.) and returned in the response.

Architecture: Edge-Optimized Image Delivery

Here is the architecture that works reliably at scale:

User request
    -> Edge Function (nearest node)
       -> Cache HIT? -> Serve from edge cache (sub-5ms)
       -> Cache MISS?
          -> Fetch original from R2/S3
          -> Transform using WebAssembly image codec
          -> Store result in edge cache
          -> Serve response

The key piece: WebAssembly image codecs. Since you cannot run Sharp at the edge, you use WebAssembly-compiled versions of the same underlying codecs. The @squoosh/lib library provides WASM builds of MozJPEG, WebP encoder, AVIF encoder, and others that run in any WASM-capable runtime including V8 isolates.

Implementation on Cloudflare Workers

Here is a working image optimization Worker that handles format negotiation and resizing:

import { ImageMagick, initialize, MagickFormat } from '@imagemagick/magick-wasm';

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const imagePath = url.pathname;

    // Build cache key from path + accepted format + width param
    const width = url.searchParams.get('w') || 'original';
    const acceptsAvif = request.headers.get('Accept')?.includes('image/avif');
    const acceptsWebp = request.headers.get('Accept')?.includes('image/webp');
    const targetFormat = acceptsAvif ? 'avif' : acceptsWebp ? 'webp' : 'jpeg';
    const cacheKey = `${imagePath}__${width}__${targetFormat}`;

    // Check edge cache
    const cache = caches.default;
    const cached = await cache.match(cacheKey);
    if (cached) return cached;

    // Fetch original from R2 storage
    const original = await env.IMAGES_BUCKET.get(imagePath.slice(1));
    if (!original) return new Response('Not found', { status: 404 });

    const buffer = await original.arrayBuffer();

    // Transform (using Cloudflare Images API or WASM codec)
    // Cloudflare Images API is simpler and more powerful:
    const transformed = await env.IMAGES.transform(buffer, {
      width: width !== 'original' ? parseInt(width) : undefined,
      format: targetFormat,
      quality: 82,
      metadata: 'none',  // Strip EXIF
    });

    const response = new Response(transformed.data, {
      headers: {
        'Content-Type': `image/${targetFormat}`,
        'Cache-Control': 'public, max-age=31536000, immutable',
        'Vary': 'Accept',
      },
    });

    // Store in edge cache
    ctx.waitUntil(cache.put(cacheKey, response.clone()));
    return response;
  }
};

The Vercel Approach: next/image With Edge Delivery

For Next.js applications, you typically do not need to write a Worker from scratch. Vercel's next/image component handles the entire pipeline:

import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      width={1200}
      height={630}
      priority          // LCP image: preload immediately
      quality={85}
      alt="Hero image"
      sizes="(max-width: 768px) 100vw, 1200px"
    />
  );
}

With zero configuration, next/image delivers:

  • Automatic WebP or AVIF conversion based on browser Accept header
  • Responsive sizing via srcset
  • Lazy loading for below-the-fold images
  • Blur-up placeholder to prevent layout shift
  • Edge caching of all transformed variants

The limitation is cost: Vercel charges per image transformation for projects beyond their free tier. At high volume, the per-transformation pricing adds up quickly.

Cache Strategy: The Most Important Optimization

The edge function is fast, but the edge cache is instant. Design your cache strategy first:

Immutable assets (versioned by hash in filename): Cache-Control: public, max-age=31536000, immutable

Frequently updated assets (product photos that change): Cache-Control: public, max-age=86400, stale-while-revalidate=604800

User-generated content: Cache-Control: private, max-age=3600 — do not serve user-uploaded content from shared edge cache unless you have cleared it of EXIF and privacy data first.

The Vary: Accept header is critical for format negotiation. Without it, a Chrome user might get cached WebP served to a Safari user who gets an AVIF they cannot decode.

When to Use a Dedicated Service Instead

Edge Functions for image optimization make sense when:

  • You need sub-50ms TTFB globally
  • Your image inventory changes frequently (cannot pre-generate all variants)
  • You are already on Vercel or Cloudflare and want zero-ops infrastructure

When to use a dedicated image optimization service instead:

  • You need heavy transformation (background removal, AI upscaling, watermarking)
  • You need Sharp's full feature set (Sharp cannot run at the edge)
  • Your team does not have bandwidth to maintain custom Worker code

Optimage is a dedicated service that handles the heavy lifting that edge functions cannot: format conversion, bulk processing, EXIF stripping, resizing, video compression, and AI transcription. Use edge delivery for serving, use Optimage for processing.

Subscribe to our newsletter for weekly deep-dives on web architecture, image optimization, and performance engineering.

Summary

Edge Functions are a genuine advancement for image delivery latency. The V8 isolate constraint means you cannot run Sharp natively, but WebAssembly codecs and managed image transformation APIs fill that gap. The architecture that works at scale combines origin processing (or a service like Optimage) for heavy transformation with edge caching and delivery for globally fast serving. Next.js developers can get most of the benefit immediately through next/image with zero additional configuration.

Try Optimage Free

Compress, convert, and optimize your images in seconds. No sign-up needed to start.

Start Optimizing Free
Back to The Optimage Journal