Why you can't run headless browsers in serverless (and what to do instead)

Why you can't run headless browsers in serverless (and what to do instead)

Lambda, Vercel, Cloudflare Workers — none of them want your Chromium.

March 28, 2026 · 8 min read

You've built a feature that needs screenshots. You're running on serverless — Lambda, Vercel Functions, Cloudflare Workers, or similar. You try to install Puppeteer or Playwright and discover that headless browsers and serverless don't mix. Here's why, and what to do about it.

The fundamental mismatch

Headless Chromium is a heavy, stateful, long-running process. Serverless functions are lightweight, stateless, and short-lived. These are fundamentally incompatible:

Chromium needsServerless provides
~400MB binary50–250MB deployment limit
~300MB RAM per instance128–1024MB function memory
5–30 seconds to render a page10–30 second timeout
Persistent process for efficiencyEphemeral, stateless execution
Shared memory, /tmp accessRestricted filesystem

Platform-specific limits

AWS Lambda

Lambda's deployment package limit is 250MB (unzipped). Chromium is ~400MB. The @sparticuz/chromium package (formerly chrome-aws-lambda) compresses Chromium to ~50MB and decompresses at runtime, but you're still fighting Lambda's 512MB /tmp limit, 10-second cold starts, and 15-minute maximum execution time. It works for occasional screenshots but is fragile — Chromium updates frequently break compatibility.

Vercel Functions

Vercel's serverless functions have a 50MB deployment limit and 10-second timeout on the Hobby plan (60 seconds on Pro). Chromium doesn't fit. Vercel's own @vercel/og library works around this by using Satori (React-to-SVG converter) instead of a real browser — but it doesn't support full website screenshots, only rendering React components.

Cloudflare Workers

Workers are V8 isolates, not containers. You can't run arbitrary binaries. Cloudflare offers Browser Rendering as a separate product (in beta) that provides headless Chromium access from Workers, but it's a managed service — you're not running Chromium in the Worker itself.

Workarounds that exist

@sparticuz/chromium on Lambda: Works, but fragile. Every Chromium update risks breaking it. Cold starts are brutal (5–10 seconds). Memory usage is tight. You'll spend more time maintaining this than you think.

Browserless.io: A managed headless browser service that exposes a Puppeteer/Playwright-compatible API over WebSocket. Your serverless function connects to their browser instead of running its own. Good option if you want to keep using Puppeteer's API.

Cloudflare Browser Rendering: Cloudflare's managed browser service for Workers. Currently in beta. If you're already on Cloudflare's platform, this is the native option. Limited to Cloudflare Workers though.

The clean solution: screenshot API

The simplest approach is to not run a browser at all. Call a screenshot API from your serverless function — it's just an HTTP request, which is exactly what serverless functions are good at.

// In your Vercel/Lambda/Worker function:
export async function handler(req) {
  const { url } = req.body;

  const screenshot = await fetch(
    'https://api.nightglass.xyz/api/v1/screenshot',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.NIGHTGLASS_KEY}`,
      },
      body: JSON.stringify({ url, width: 1440, height: 900 }),
    }
  );

  const buffer = await screenshot.arrayBuffer();
  return new Response(buffer, {
    headers: { 'Content-Type': 'image/png' },
  });
}

This works in every serverless environment because it's just an HTTP call. No binaries, no /tmp juggling, no cold start penalty, no Chromium version management. The browser runs on nightglass's infrastructure — dedicated hardware optimised for headless browsers, not squeezed into a Lambda function.

Cost comparison

Lambda + chrome-aws-lambda: ~$0.002–0.01 per screenshot (compute cost) plus engineering time for maintenance. nightglass API: $0.005 per screenshot, no engineering time for browser management. If you value your time at more than $20/hour and you'll spend more than an hour per month maintaining chrome-aws-lambda, the API is cheaper.

Bottom line: Serverless and headless browsers are a bad fit. The workarounds are brittle and expensive to maintain. A screenshot API turns the problem into a simple HTTP call, which is what serverless functions are actually designed for.

To compare APIs, see the 2026 screenshot API comparison. If you want to self-host on a dedicated server instead, the Node.js screenshot microservice guide walks through a production setup. For general headless browser tradeoffs, the complete headless browser guide.