← Back to Home

Playwright for Serverless

Chromium is ~280MB. Lambda's limit is 250MB. Vercel caps at 50MB. Cloudflare Workers can't run it at all. We solved all of these.

Recognize These?

Unzipped size must be smaller than 262144000 bytes

Chromium is ~280MB. Lambda's unzipped limit is 250MB. The math doesn't work, no matter how clever you get with compression.

spawn ENOMEM

Lambda ran out of memory trying to spawn Chrome. You need 512MB minimum, 1-2GB recommended. That gets expensive.

error while loading shared libraries: libnss3.so

Missing Linux dependencies on Amazon Linux. Time to debug Lambda layers and figure out which .so files are missing.

Task timed out after 30 seconds

Cold start + Brotli decompression + Chrome launch + page render exceeded API Gateway's limit. And that's on a fast day.

If you've seen these errors, you know the pain. We built Riddle so you don't have to deal with this anymore.

The Serverless Browser Problem

Every major serverless platform has limits that make headless Chrome difficult or impossible:

AWS Lambda

250MB unzipped limit

Chromium is 280MB. @sparticuz/chromium compresses to ~45MB but decompresses to /tmp on every cold start. VPC-enabled Lambdas can take 5+ minutes.

Vercel Functions

50MB compressed limit

Even aggressive compression doesn't work. Developers are stuck pinning ancient Puppeteer versions from 2021. Modern features are out of reach.

Cloudflare Workers

V8 isolates, no browser

Workers run JavaScript, not containers. You literally cannot run Chrome. Cloudflare's Browser Rendering API exists but is rate-limited to 2/min.

The @sparticuz/chromium Trap

Over 300,000 developers download @sparticuz/chromium every week. Most of them are frustrated within hours.

Version Pinning Hell

Chromium version must match Puppeteer version must match Node.js runtime. AWS updates break your stack. You spend hours debugging compatibility.

Cold Start Penalty

Brotli decompression on every cold start. 5-10 seconds before your function even starts working. In VPCs, this can take minutes.

Memory Instability

Chrome loves RAM. Lambda doesn't have much. Your function crashes randomly on complex pages. "spawn ENOMEM" becomes your new nemesis.

Runtime Update Roulette

April 2024's Node.js 18.20 update froze Puppeteer for thousands of developers. You're one security patch away from a broken production system.

As one developer put it: "We had been burned by using third-party chromium packages and things could easily break with one security patch from AWS."

The Solution: Don't Run Chrome in Lambda

Instead of fighting Lambda limits, call an API. We run Playwright in proper infrastructure so you don't have to.

1

Your Lambda calls Riddle

POST a URL or a Playwright script. We handle the browser.

2

We run Chrome properly

No size limits, no memory constraints, no cold start decompression. Just Chrome running on real infrastructure.

3

You get your screenshot

PNG delivered to S3. Poll for completion or use webhooks. Download when ready.

Before and After

Before: Lambda with @sparticuz/chromium (50+ lines of config)

// serverless.yml - just the chromium layer config
layers:
  chromium:
    path: layer
    compatibleRuntimes:
      - nodejs18.x
    package:
      artifact: layer/chromium.zip

// handler.js - and pray it works
const chromium = require("@sparticuz/chromium");
const puppeteer = require("puppeteer-core");

exports.handler = async (event) => {
  // Configure for Lambda's weird environment
  chromium.setHeadlessMode = true;
  chromium.setGraphicsMode = false;

  const browser = await puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath: await chromium.executablePath(),
    headless: chromium.headless,
  });

  // Hope Chrome doesn't crash...
  const page = await browser.newPage();
  await page.goto(event.url);
  const screenshot = await page.screenshot();

  await browser.close();
  return screenshot;
};

After: One API call

// That's it. No layers, no binaries, no config.
const response = await fetch("https://api.riddledc.com/v1/run", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ url: "https://example.com" })
});

const { job_id } = await response.json();
// Poll for completion, get your screenshot

Full Comparison

FactorSelf-Hosted LambdaRiddle API
Setup timeHours of debugging layers, binaries, dependenciescurl command in 2 minutes
Size limit250MB (Chromium is 280MB)Not your problem
Memory needed1-2GB minimum, crashes commonWhatever your Lambda actually needs
Cold start5-10s (Brotli decompression)~2s (HTTP call)
MaintenanceBreaks with Node.js runtime updatesWe handle it
Version compatibilityChromium + Puppeteer + Node.js matrixAlways latest
Works on VercelBarely (ancient versions only)Yes
Works on CloudflareNoYes
Cost per job~$0.001 + compute + your debugging timefrom $0.004 (all-in)

Read the detailed breakdown →

What You Can Do

Simple Screenshots

POST a URL, get a PNG. Pass auth headers to screenshot staging environments and protected pages.

JSON Workflows

Multi-page flows, form fills, clicks, waits. Send JSON steps, no code required.

Batch Screenshots

Take multiple screenshots in one job. Get below $0.001 per screenshot with batching.

PDF Generation

Render pages to PDF with full CSS support. No more wkhtmltopdf headaches.

Simple Pricing

from $0.004
Per job

30-second minimum

<$0.001
Per screenshot

With batching (multiple per job)

No subscriptions. No monthly minimums. No "units" to figure out. Just pay for browser time.

See how to maximize value → | Screenshot authenticated pages →

Stop Fighting Lambda

Create an account and get screenshots in minutes, not hours.