DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to take screenshots in AWS Lambda (without Chromium layers)

How to Take Screenshots in AWS Lambda (Without Chromium Layers)

Getting Chromium to run in AWS Lambda is a rite of passage nobody enjoys. The standard approach: add @sparticuz/chromium or chrome-aws-lambda, configure a Lambda layer, stay under the 250MB deployment limit, fight cold start times, then discover Lambda's /tmp storage is too small for your screenshots.

There's a simpler path: don't run Chromium in Lambda. Make an API call instead.

The Lambda function

// handler.js
export const handler = async (event) => {
  const { url, filename } = event;

  const res = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.PAGEBOLT_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url,
      fullPage: true,
      blockBanners: true
    })
  });

  const buffer = Buffer.from(await res.arrayBuffer());

  // Save to S3, return base64, or pass downstream
  return {
    statusCode: 200,
    body: buffer.toString('base64'),
    isBase64Encoded: true,
    headers: { 'Content-Type': 'image/png' }
  };
};
Enter fullscreen mode Exit fullscreen mode

Zero dependencies beyond Node's built-in fetch (available in Node 18+). Deployment package is kilobytes, not hundreds of megabytes. Cold starts are instant.

Save to S3

For screenshot pipelines that store results:

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({});

export const handler = async (event) => {
  const { url, key } = event;

  const res = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: { 'x-api-key': process.env.PAGEBOLT_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ url, fullPage: true, blockBanners: true })
  });

  const buffer = Buffer.from(await res.arrayBuffer());

  await s3.send(new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key || `screenshots/${Date.now()}.png`,
    Body: buffer,
    ContentType: 'image/png'
  }));

  return { statusCode: 200, key };
};
Enter fullscreen mode Exit fullscreen mode

Generate a PDF

Swap the endpoint:

body: JSON.stringify({
  url,
  format: 'A4',
  printBackground: true
})
// and POST to /v1/pdf instead of /v1/screenshot
Enter fullscreen mode Exit fullscreen mode

PDF generation via Chromium in Lambda is even harder than screenshots — Lambda's sandboxed environment often breaks Page.printToPDF. With an API call it's identical to the screenshot pattern.

Trigger on a schedule (EventBridge)

{
  "source": "aws.events",
  "detail-type": "Scheduled Event",
  "resources": ["arn:aws:events:us-east-1:123:rule/screenshot-cron"],
  "detail": {
    "url": "https://yoursite.com/dashboard",
    "key": "monitoring/daily.png"
  }
}
Enter fullscreen mode Exit fullscreen mode

Pair with an EventBridge cron rule to capture daily screenshots of any URL — competitor monitoring, status page archives, visual regression baselines.

Environment variable setup

PAGEBOLT_API_KEY=your_key_here
S3_BUCKET=your-bucket-name
Enter fullscreen mode Exit fullscreen mode

Set both as Lambda environment variables in the console or via Terraform/CDK. No layers, no extensions, no binary compatibility issues.

What you skip

The @sparticuz/chromium approach requires:

  • A Lambda layer (~40MB compressed)
  • Setting executablePath to the layer binary
  • Configuring --single-process, --no-zygote, --disable-dev-shm-usage
  • Bumping Lambda memory to 1-2GB for Chromium to launch
  • Accepting 5-10s cold starts when the container initializes

With an API call, your Lambda stays at 128MB, deploys in seconds, and cold starts in milliseconds. The browser runs on PageBolt's infrastructure.


Free tier: 100 requests/month, no credit card. → pagebolt.dev

Top comments (0)