Back to Blog
Technical

Client-Side vs. Server-Side Image Generation: Building a Local-First Web App

June 28, 20269 min read

BENCHMARK PERFORMANCE RATIOS

Puppeteer (SSR)

5200ms

Cloud Cost / Cold Starts

HTML5 Canvas (CSR)

18ms

Local-Only / Free & Instant

When building a web application that exports customized graphics—such as screenshot mockups, social sharing cards, or certificates—developers face a major architectural decision: should the image rendering take place client-side in the user's browser, or server-side on a dedicated server or serverless function?

Both approaches have unique trade-offs regarding rendering speeds, server operating costs, developer effort, and user privacy. In this deep dive, we will analyze client-side rendering (using HTML5 Canvas and DOM serialization) versus server-side rendering (using Puppeteer and Chromium backend stacks), explaining why we built ByteFrame as a local-first application.

Understanding Server-Side Generation (SSR)

Server-side rendering is the traditional approach. Typically, a backend API endpoint takes a JSON structure, opens a headless browser instance (like Puppeteer or Playwright), loads a webpage containing the styled HTML layout, captures a viewport screenshot, and returns the rasterized binary PNG to the client.

  • Pros: High layout consistency since all rendering occurs inside a uniform browser environment. Custom fonts and SVGs render reliably.
  • Cons: Cold start latency of serverless functions and headless browser spin-up can take 3 to 10 seconds. High server billing costs under load, because headless Chromium instances are memory-heavy.
  • Security Risk: User images must be uploaded to your server. If users are capturing internal dashboards or database charts, uploading them presents a compliance risk.

Understanding Client-Side Generation (CSR)

Client-side generation bypasses the server entirely. Utilizing standard browser APIs, such as HTML5 Canvas, SVG foreignObject serialization, and the Web Cryptography API, the browser processes the visual transformations and saves the output directly from memory.

canvas-export.jsjavascript
// Dynamic high-DPI canvas render
function exportRetinaCanvas(canvasId, scaleFactor = 2) {
  const canvas = document.getElementById(canvasId);
  const ctx = canvas.getContext('2d');
  
  // Scale canvas coordinates for high-density screens
  canvas.width = canvas.clientWidth * scaleFactor;
  canvas.height = canvas.clientHeight * scaleFactor;
  ctx.scale(scaleFactor, scaleFactor);
  
  // Render visual assets locally...
  const dataUrl = canvas.toDataURL('image/png');
  return dataUrl;
}

By performing the calculations directly inside the client's rendering loop, the application achieves instant feedback. Moving sliders updates shadows, paddings, and background gradients in real-time, and exporting is immediate.

For local-first graphics libraries (such as html-to-image), ensure that all assets—like custom Google Web Fonts and external images—are fully encoded into Base64 Data URLs before rendering. This prevents CORS errors and font rendering fallback failures.

Architectural Comparison

At ByteFrame, our design requirement is "Privacy First & Local-Only". By keeping all image data inside the client browser, we make it impossible for customer screenshots to leak or be intercepted on transit. Additionally, this local-first model lets us support batch operations (processing 10+ screenshots into ZIP archives simultaneously) without incurring hosting costs, allowing us to keep the service free and fast.