ScreenshotNeo

BlogHow-to

How to Use chrome-aws-lambda with Puppeteer on AWS Amplify

Learn where chrome-aws-lambda fits in AWS Amplify, how to package Puppeteer for Lambda, and why Amplify SSR needs a separate compatibility check.

By the ScreenshotNeo team1 October 20269 min read

Short answer: first decide where Chromium will run. A dedicated AWS Lambda function and an AWS Amplify Hosting SSR compute application use different deployment contracts. The chrome-aws-lambda README shows a Lambda-oriented launch pattern, but its listed 10.1 package line bundles Chromium 92.0.4512.0, while current Amplify SSR documentation lists Node.js 20, 22, and 24. The available documentation does not verify that this older browser package works in today’s Amplify SSR runtime, so treat an Amplify SSR deployment as a compatibility investigation rather than a guaranteed recipe.

If your application is hosted by Amplify but browser work can run in a separate function, the lowest-risk interpretation of the package documentation is: package chrome-aws-lambda and the matching Puppeteer version in a Lambda ZIP or layer, launch with the package’s executable path and arguments, and validate the exact runtime, architecture, memory, timeout, and deployment size on the target function.

1. Identify the execution target

“On AWS Amplify” can mean two different things:

Target What runs Packaging model What to verify
Separate AWS Lambda function A Lambda handler launches Chromium and returns a result Deployment ZIP or Lambda layer containing the function and dependencies Node runtime, CPU architecture, executable extraction, permissions, memory, timeout, and package size
Amplify Hosting SSR compute A Node.js HTTP server handles requests on port 3000 Self-contained compute bundle emitted by the framework adapter Supported Node major version, browser binary and native libraries, 220 MB uncompressed bundle limit, 512 MB ephemeral storage, and 15-minute maximum execution time

A Lambda layer recipe should not be copied into an Amplify SSR bundle unchanged. Amplify’s deployment specification requires a self-contained Node.js server bundle; it is not a Lambda handler contract. The specification says: “The entry point file must be a Node.js module and it must start an HTTP server that listens on port 3000.” Read the Amplify deployment specification.

2. Check compatibility before writing application code

  • Package revision: the chrome-aws-lambda version table lists the 10.1 line with Chromium 92.0.4512.0. That is a historical mapping, not proof of compatibility with a current Amplify runtime.
  • Amplify SSR Node versions: current supported-features documentation lists Node.js 20, 22, and 24. Next.js compute uses the Node major version used to build the application.
  • Build versus deployed runtime: Amplify build configuration selects a Node version for the build image. The build version and the deployed SSR runtime are related, but they are separate configuration facts.
  • Architecture and native libraries: confirm that the Chromium binary and its shared libraries match the architecture and operating-system environment used by the target.
  • Size and storage: an Amplify compute bundle must stay within the documented 220 MB uncompressed limit and has 512 MB of ephemeral storage. Do not confuse that storage figure with the Lambda memory guidance in the package README.

Amplify recommends testing an application on a new branch before a Node.js upgrade. Use that same practice for a browser-package change, because a successful local install does not establish that the deployed binary can execute.

3. Lambda implementation documented by chrome-aws-lambda

The following is the launch pattern shown by the package README. It is a Lambda example, not a verified Amplify SSR deployment. Install chrome-aws-lambda and a corresponding puppeteer-core or puppeteer version as required by the package’s version guidance. See the chrome-aws-lambda README and version table.

const chromium = require('chrome-aws-lambda');

exports.handler = async (event) => {
  let browser;
  try {
    browser = await chromium.puppeteer.launch({
      args: chromium.args,
      defaultViewport: chromium.defaultViewport,
      executablePath: await chromium.executablePath,
      headless: chromium.headless,
    });

    const page = await browser.newPage();
    await page.goto(event.url || 'https://example.com');
    return await page.title();
  } finally {
    if (browser) await browser.close();
  }
};

The finally block matters. Browser processes consume memory and file descriptors; leaving one open can cause later invocations to fail or a warm execution environment to accumulate processes.

Lambda packaging checklist

  1. Choose the Lambda Node.js runtime and CPU architecture.
  2. Install the package and its matching Puppeteer dependency in the function project.
  3. Deploy them in the function ZIP or in a Lambda layer. AWS documents both approaches and recommends keeping dependency versions under your control.
  4. Set timeout and memory for the pages you actually load. The package README says to use at least 512 MB of Lambda memory and recommends 1600 MB or more.
  5. Invoke the deployed function with a URL and inspect logs for executable-path, missing-library, navigation, and timeout errors.
  6. Repeat the test after every Node, Puppeteer, Chromium, or architecture change.

4. Calling a Lambda browser worker from an Amplify app

A common architecture is an Amplify-hosted frontend or SSR application that calls a separate Lambda browser worker. The SSR request remains a normal web request, while the worker owns Chromium’s packaging and resource requirements.

// Example server-side call from your Amplify application.
const response = await fetch(process.env.BROWSER_WORKER_URL, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ url: 'https://example.com' }),
});

if (!response.ok) {
  throw new Error(`Browser worker failed: ${response.status}`);
}

const title = await response.text();

Keep the worker URL private or authenticated. Do not accept arbitrary destinations without an allowlist or equivalent controls; a browser worker that can fetch any URL needs an explicit policy for outbound requests.

5. What changes for Amplify SSR compute?

Amplify Hosting SSR uses a Node.js HTTP server bundle produced by the framework adapter. The entry point listens on port 3000, and the compute output must contain everything needed at runtime. You must therefore prove all of the following in the actual deployed artifact:

  • The browser executable is present at runtime and can be started by the deployed user.
  • Every required shared library is present in the runtime image.
  • The package’s Node and Chromium revisions work with the Node major version used to build and deploy the SSR application.
  • The uncompressed bundle remains at or below 220 MB.
  • Temporary extraction and profile files fit within 512 MB of ephemeral storage.
  • A request that launches a browser completes within the 15-minute execution limit.

The sources reviewed do not show an end-to-end chrome-aws-lambda deployment on Amplify SSR. Do not present the Lambda example as proof that the SSR path is supported. If the package revision is unsuitable, select a browser binary and Puppeteer release that you can validate against the target runtime; the available research does not establish a specific replacement.

Minimal HTTP server shape

This illustrates the Amplify compute contract only. It does not solve Chromium packaging:

const http = require('node:http');

const server = http.createServer(async (req, res) => {
  if (req.url === '/health') {
    res.writeHead(200, { 'content-type': 'text/plain' });
    res.end('ok');
    return;
  }

  res.writeHead(404);
  res.end();
});

server.listen(3000);

6. Navigation and browser lifecycle details

For real pages, make navigation behavior explicit. A page can return a 200 response while its application is still rendering, or it can never reach network idle because of analytics and streaming connections.

const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900 });
await page.goto(targetUrl, {
  waitUntil: 'domcontentloaded',
  timeout: 30_000,
});
await page.waitForSelector('#content', { timeout: 10_000 });
const title = await page.title();
  • Use domcontentloaded when you need the initial document quickly.
  • Use a selector wait when a known application element signals readiness.
  • Use a bounded timeout for every navigation and selector wait.
  • Close pages and browsers in cleanup paths, including error paths.
  • Reuse a warm browser only after measuring isolation and memory behavior; a fresh context or page still needs cleanup.

7. Troubleshooting

Symptom Likely cause Fix
ENOENT or executable not found The binary was not packaged, extracted, or addressed with the package executable path. Inspect the deployed ZIP or bundle, log the resolved executable path, and verify extraction permissions.
Browser closes immediately Missing shared libraries, incompatible architecture, or an unsupported Chromium/Node combination. Check runtime architecture and native dependencies; validate the exact package revision on the target.
Navigation timeout The site is slow, keeps connections open, blocks the cloud IP, or waits for client-side rendering. Set a bounded timeout, choose an appropriate waitUntil, wait for a specific selector, and inspect the destination independently.
Works locally but fails after deployment Local Chrome and the deployed Chromium binary do not share the same environment. Test the packaged artifact in a matching runtime and inspect deployment logs.
Amplify deployment rejects the bundle Wrong SSR output structure, non-self-contained compute output, or bundle over 220 MB. Follow the framework adapter’s Amplify output contract and measure the uncompressed compute bundle.
Out-of-memory or disk errors Chromium plus page assets exceed available memory or temporary storage. Reduce concurrency, close pages, limit downloads, and distinguish Lambda memory from Amplify’s 512 MB ephemeral storage.
Requests fail only in production Bot checks, geolocation, headers, cookies, or outbound-network policy differ. Compare request headers and network policy, and record the first failing navigation URL and status.

8. Performance, reliability, and cost considerations

Performance

  • Launching Chromium is expensive; avoid launching multiple browsers per request.
  • Reuse a browser carefully in warm Lambda environments, but create isolated pages or contexts and always close them.
  • Limit parallel pages to the memory available for the function.
  • Use selector waits instead of an unnecessarily long fixed delay.
  • Block unneeded resources only when your page’s correctness allows it.

Reliability

  • Record the target URL, navigation timing, browser revision, runtime version, and failure stage.
  • Retry only transient failures, with a cap and backoff. Do not retry deterministic packaging errors.
  • Keep browser work off the interactive SSR request path when a slow or untrusted destination could delay users.
  • Deploy browser changes on a branch and validate the real target before promoting them.

Cost

Lambda and Amplify charges depend on the AWS services, memory, duration, requests, and data transfer in your account. The research sources do not provide a workload benchmark or universal cost estimate. Measure your own browser duration and concurrency rather than treating package memory guidance as a price forecast.

9. Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF, so your Amplify application does not need to package Chromium.

Read the ScreenshotNeo API documentation for the full option set. Basic cURL:

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. You can use full-page capture, CSS element capture, device presets, custom viewport and retina scale, dark mode, PDF settings, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs, webhooks, bulk capture, and the usage API.

Free accounts include 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

10. Decision checklist

  • Have you named the execution target: Lambda or Amplify SSR compute?
  • Does the Node major version match the actual build and deployment configuration?
  • Have you paired Puppeteer with the Chromium revision required by the package?
  • Are the executable, native libraries, and dependencies inside the deployed artifact?
  • Did you check Lambda memory separately from Amplify ephemeral storage?
  • Does the Amplify compute bundle fit the 220 MB uncompressed limit?
  • Are browser launches bounded by timeouts and cleaned up in finally?
  • Have you tested the packaged artifact on the real deployment target?

FAQ

Does chrome-aws-lambda work on AWS Amplify?

The available sources establish a Lambda launch pattern, but they do not verify this package line on current Amplify SSR compute. A working Lambda design and a working Amplify SSR design require separate validation.

Can I use a Lambda layer in Amplify SSR?

Do not assume so. Lambda layers belong to the Lambda packaging model; Amplify SSR requires a self-contained compute bundle.

Why does 512 MB appear in both guides?

The package README recommends at least 512 MB of Lambda memory. Amplify documents 512 MB of ephemeral storage. They are different resources.

Should browser work run during an SSR request?

Only when the latency, timeout, memory, and failure behavior are acceptable for that request. A separate worker is often easier to isolate and operate.