DEV Community

göktürk kahriman
göktürk kahriman

Posted on • Originally published at kreotar.com

Client-side vs Server-side image processing: When to use what? (Based on 2 years of building Kreotar)

I spent 18 months convinced client-side was always better. Then I tried processing a 100MB TIFF file on a 2015 MacBook Air. The browser tab died. The user (my beta tester) was not amused.
Here's my honest breakdown of when to keep it in the browser versus shipping it to a server, based on building Kreotar's image processing suite (50+ tools, from compression to color grading).
The Client-Side Advantage (What Kreotar Uses)
Privacy is non-negotiable: When you're handling medical images, sensitive screenshots, or proprietary designs, client-side is the only ethical choice. The data never traverses the network unencrypted, never sits on someone else's hard drive.
Latency for small files: A 2MB JPEG compression happens in ~200ms client-side. The same operation via API: 50ms upload + 100ms processing + 50ms download = 200ms. Tie. But add network variance and the client-side wins for sub-5MB files.
Offline capability: Kreotar works in airplanes. In basements. In paranoid corporate networks that block everything.
Cost scaling: I pay for hosting. You pay nothing to use the tool. If I had to process millions of images on servers, I'd need to charge or show ads. Client-side keeps it sustainably free.


// Client-side image processing pipeline (Kreotar's actual architecture)
const processImageClientSide = async (file, operations) => {
  // Load WASM image processing library (libvips compiled to WASM)
  const image = await Vips.Image.newFromBuffer(await file.arrayBuffer());

  let processed = image;

  operations.forEach(op => {
    switch(op.type) {
      case 'resize':
        processed = processed.resize(op.scale);
        break;
      case 'sharpen':
        processed = processed.sharpen(op.options);
        break;
      case 'format':
        processed = processed.writeToBuffer(op.format);
        break;
    }
  });

  return processed.writeToBuffer('.jpg');
};
Enter fullscreen mode Exit fullscreen mode

**When Server-Side Wins (My Mistakes)
**Heavy batch operations: Converting 100 RAW photos to JPEG? Your browser will melt. A server with 32GB RAM and multi-core CPU won't blink.
AI/ML inference: Kreotar's background remover uses a tiny ONNX model (~4MB) that runs client-side for 90% of use cases. But for 4K professional photos? We offload to GPU servers. The 50MB model loads too slowly in browsers.
Persistent storage needs: If you need to save versions, maintain libraries, or collaborate, you need a database. LocalStorage has a 5-10MB limit. IndexedDB is better but complex.
Proprietary algorithms: Some compression techniques are trade secrets. You can't ship that WASM without exposing your IP.
The Hybrid Approach (What I Actually Do)
Kreotar uses a "capability detection" pattern:

const processWithFallback = async (file, settings) => {
  const clientCapable = checkClientCapabilities();

  // Heavy file or complex operation?
  if (file.size > 50 * 1024 * 1024 || settings.aiEnhance) {
    // Server fallback
    return await uploadToProcessingServer(file, settings);
  }

  try {
    // Attempt client-side first
    return await processImageClientSide(file, settings);
  } catch (error) {
    // Out of memory? Fall back to server
    if (error.message.includes('memory')) {
      console.warn('Client OOM, falling back to server');
      return await uploadToProcessingServer(file, settings);
    }
    throw error;
  }
};
Enter fullscreen mode Exit fullscreen mode

The Honest Trade-offs

  • Factor Client-Side Server-Side
  • Privacy ✅ Perfect ❌ Trust required
  • Speed (<5MB) ✅ Fast ⚠️ Network dependent
  • Speed (>50MB) ❌ Slow/crashes ✅ Optimized
  • Device battery ❌ Drains fast ✅ Zero impact
  • Complex algorithms ⚠️ Limited by WASM ✅ Unlimited
  • Cost to operator ✅ Low (static hosting) ❌ High (compute) My Hot Take **We're entering the "edge computing" middle ground. Cloudflare Workers and Deno Deploy allow WASM execution at the edge - close to the user but not on their device. For Kreotar v2, I'm experimenting with this for the "Goldilocks zone" files (10-50MB). What are you building that needs client-side processing? I'm particularly curious about: Are you hitting WASM bundle size limits? (Kreotar's image toolkit is 2.3MB compressed - acceptable?) Do your users care about privacy, or do they prefer convenience? Have you tried the new WebGPU APIs for parallel image processing? Drop your use case below. If it's interesting enough, I might build a tool for it. **Side note: I once accidentally shipped a development build with console.log statements in the WASM glue code. A user processed 1000 images and sent me a screenshot of their browser console - 3000 lines of debug output. Always strip your debug builds, friends. 🔧

Top comments (0)