Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use either a Lambda layer with a ZIP-deployed function or a Lambda container image. A layer is the reusable option: build a Linux-compatible archive with the required Node.js directory, publish it, and attach its versioned ARN. A container image is the practical fallback when Chromium and its libraries make ZIP limits awkward; the runtime, application, browser, and dependencies are built into one image.

This guide shows both deployment paths with @sparticuz/chromium and puppeteer-core, explains architecture and version matching, and gives fixes for the failures that commonly appear only in Lambda.

Choose the packaging model first

Concern ZIP function plus layer Container image
Reuse A published layer can be attached to several functions. Reuse an image tag or digest through your container registry.
Where dependencies live Function dependencies are in the deployment ZIP; shared browser files are in the layer and extracted under /opt. The runtime, application, Chromium, and all libraries are inside the image. Layers cannot be attached.
Size pressure Subject to Lambda ZIP, layer, and aggregate uncompressed limits; a browser often makes this route difficult. Lambda allows up to 10 GB uncompressed for a container image.
Best fit Several functions need the same browser build and the package fits comfortably. The browser stack is large or you want one immutable, reproducible artifact.
Architecture Publish a layer built for the function’s x86_64 or arm64 architecture. Build the image and include Chromium binaries for the selected Lambda architecture.

Lambda permits up to five layers on one function. A layer is a ZIP archive containing supplementary code or data; Lambda unpacks it under /opt. For Node.js, the archive must use the runtime-specific layout described below.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites and compatibility checks

  • Choose the exact Lambda Node.js runtime before installing packages. Build Node.js layer content with the same runtime version used by the function.
  • Build on Linux compatible with Lambda’s Amazon Linux environment. A package assembled on macOS or Windows can contain incompatible native modules.
  • Confirm the function architecture in Lambda configuration: x86_64 and arm64 require different Chromium artifacts.
  • Use puppeteer-core or Playwright as the automation client and @sparticuz/chromium as the serverless Chromium distribution.
  • Pin the Chromium and automation-client versions. Sparticuz follows Chromium releases rather than ordinary semantic versioning, so a patch-level update can contain breaking changes.

@sparticuz/chromium is not tied to a particular Puppeteer version, but compatibility still has to be checked for the versions you select. Its minimal distribution can also use a remotely hosted Chromium pack; that option requires the network access and configuration documented by the project.

Pattern 1: package Chromium in a Lambda layer

Build the layer directory

For a Node.js layer, place dependencies under nodejs/node_modules. Some runtimes also support a runtime-specific path such as nodejs/node20/node_modules; use the convention required by your selected runtime.

  1. Start a Linux build environment matching Lambda’s operating system and architecture. A CI runner or an Amazon Linux-compatible container avoids native-binary surprises.
  2. Create the layer tree and install production dependencies:
mkdir -p layer/nodejs
cd layer/nodejs
npm init -y
npm install --omit=dev puppeteer-core @sparticuz/chromium
cd ../..
zip -r chromium-layer.zip nodejs

If the browser is supplied entirely by a separate layer, keep @sparticuz/chromium as a development dependency in the function package only when that layer’s documentation explicitly supports it. Otherwise, include the package in the production layer as shown.

Publish and attach the layer

  1. In the AWS Lambda console, open Layers and choose Create layer.
  2. Upload chromium-layer.zip, select compatible runtimes, and select the matching architecture.
  3. Create the layer, then open the function’s Code or Configuration page, choose Layers, and add the layer by ARN and version.
  4. Deploy the function ZIP containing your handler and any application-only dependencies.

Lambda extracts the layer at /opt. Do not hard-code a path from your workstation; use the Chromium package’s executablePath helper, which resolves its unpacked location for the invocation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Node.js handler using Puppeteer

Set the handler to index.handler and use this complete example:

const chromium = require('@sparticuz/chromium');
const puppeteer = require('puppeteer-core');

exports.handler = async (event) => {
  const url = event.url || 'https://example.com';
  let browser;
  try {
    browser = await puppeteer.launch({
      args: chromium.args,
      defaultViewport: chromium.defaultViewport,
      executablePath: await chromium.executablePath(),
      headless: true
    });
    const page = await browser.newPage();
    await page.goto(url, {waitUntil: 'networkidle2', timeout: 30000});
    const png = await page.screenshot({type: 'png', fullPage: true});
    return {
      statusCode: 200,
      headers: {'content-type': 'image/png'},
      isBase64Encoded: true,
      body: png.toString('base64')
    };
  } finally {
    if (browser) await browser.close();
  }
};

The finally block matters: a warm execution environment can be reused, and leaked browser processes consume memory and file descriptors. Set Lambda memory and timeout high enough for your pages, but do not assume a universal startup or rendering time; it depends on the page, architecture, browser build, and concurrency.

Pattern 2: build a Lambda container image

Use this route when the ZIP and layer aggregate limits cannot accommodate the browser or when you want the complete runtime stack versioned as one artifact. A container-image function cannot have layers attached.

Dockerfile based on the AWS Node.js image

Place index.js and package.json beside this Dockerfile. The AWS base image supplies the Lambda runtime interface:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
FROM public.ecr.aws/lambda/nodejs:20

COPY package*.json ${LAMBDA_TASK_ROOT}/
RUN npm ci --omit=dev
COPY index.js ${LAMBDA_TASK_ROOT}/

CMD ["index.handler"]

Your package.json should list pinned production versions:

{
  "type": "commonjs",
  "dependencies": {
    "@sparticuz/chromium": "PINNED_VERSION",
    "puppeteer-core": "PINNED_VERSION"
  }
}

Build and push the image with a tag or digest, then create or update the Lambda function from that image. Build for the same architecture selected in Lambda; an image containing x64 Chromium cannot run as an arm64 function. If you use an OS-only or alternative base image instead of an AWS language base image, add the Lambda runtime interface client and configure its entrypoint as required for that base.

Runtime options that affect real pages

Viewport, full-page output, and lazy content

Use chromium.defaultViewport as a safe baseline, then set an explicit viewport for deterministic layouts. Full-page screenshots can trigger lazy-loaded assets and increase memory use. For long pages, consider capturing a selected element or splitting the work across invocations.

Navigation and network behavior

networkidle2 can wait indefinitely on applications that maintain open connections. Use a finite timeout and a more suitable lifecycle event when necessary. Pages protected by bot checks or CAPTCHAs may never produce usable output; treat those responses as an application-level failure rather than retrying indefinitely.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Temporary storage and concurrency

Chromium unpacks files during startup and uses temporary storage. Ensure the function’s ephemeral storage is sufficient for your selected browser distribution and page workload. High concurrency multiplies memory and temporary-file demand; set reserved or account concurrency deliberately and monitor errors before increasing it.

Size, version, and release management

  • Install with production-only dependencies and remove test fixtures, documentation, and unused browser assets from ZIP builds.
  • Keep the browser package and automation client pinned in source control. Upgrade them together in a test function.
  • Record the Lambda runtime, architecture, Chromium package version, Puppeteer version, and layer version in deployment metadata.
  • Publish a new layer version rather than mutating an existing one, so rollback is a single configuration change.
  • Use a container image digest for repeatable deployments instead of relying only on a mutable tag.

Troubleshooting common failures

“Cannot find module” or missing executable

Cause: the layer ZIP has an extra top-level directory, the Node.js path is wrong, or the layer is not attached to the published function version.

Fix: open the ZIP and verify that nodejs/node_modules is at its root, confirm the layer ARN and version on the deployed function, and log await chromium.executablePath() during a test invocation.

Exec format error

Cause: an x64 binary is running on arm64, or the reverse.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: align the Lambda architecture, layer or image platform, and Chromium artifact. Rebuild native dependencies in the target architecture.

Browser closes immediately

Cause: missing launch arguments, an incompatible Chromium/client pair, insufficient memory, or a corrupted unpack directory.

Fix: use the package-provided args, defaultViewport, and executablePath; verify pinned versions; increase memory for a diagnostic invocation; and ensure temporary storage is writable.

Timeouts on page.goto

Cause: slow third-party resources, persistent connections, bot challenges, or a lifecycle condition that never becomes idle.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: set a finite navigation timeout, choose domcontentloaded where appropriate, wait for a specific selector, and capture diagnostic logs. Do not treat a CAPTCHA as a transient network error.

ZIP or layer size rejection

Cause: the browser and dependencies exceed the ZIP or aggregate uncompressed limits.

Fix: remove development files and unused assets, split genuinely shared dependencies into layers (within Lambda’s five-layer maximum), or move the complete stack to a container image, which supports up to 10 GB uncompressed.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When you do not need to operate Chromium yourself

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF, while its capture flow accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For the one-call API, see the ScreenshotNeo documentation:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Every plan includes the same features, including selectors, dark mode, device presets, custom CSS and JavaScript, request blocking, cookies and headers, geolocation, PDFs, signed links, async jobs, bulk capture, caching, and a usage API. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Frequently Asked Questions

Can a container-image Lambda also use a Chromium layer?

No. Container-image functions package dependencies in the image and do not support attached Lambda layers.

Is Playwright required for this setup?

No. The documented example uses Puppeteer Core, while the Sparticuz distribution can also be used with Playwright when its launch configuration and version compatibility are satisfied.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which architecture should I choose?

Choose the architecture supported by your surrounding dependencies and deployment environment, then use the matching Chromium artifact consistently in the layer or image.

Does a newer Sparticuz patch release guarantee compatibility?

No. The project follows Chromium releases rather than standard semantic versioning, so review its compatibility guidance and release notes before upgrading.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.