Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Use Vercel for authentication and request handling, and AWS Lambda for headless-Chromium rendering. Put report HTML and assets in private S3, invoke a Lambda function through a Function URL or API Gateway, and return either the PDF (for short jobs) or a job ID whose result is a short-lived S3 URL (for slow or bursty jobs). For production workloads, the queued design—SQS, a worker Lambda, DynamoDB status, retries and a dead-letter queue—is safer than keeping an HTTP request open.
Reference architecture
A Vercel Route Handler is the public API. It authenticates the caller, validates the report request and creates a presigned S3 upload so large HTML, images and fonts do not pass through the browser-facing function. The client uploads the input to S3, then Vercel invokes a renderer.
- Client sends report metadata to a Vercel Route Handler.
- Vercel creates a job ID and a presigned S3 upload (a presigned POST is also suitable for browser uploads).
- The client uploads HTML and assets to a private S3 prefix.
- Vercel invokes Lambda through a Function URL or API Gateway.
- Lambda launches a Lambda-compatible Chromium build, renders the page and writes the PDF to S3.
- For synchronous jobs, Lambda returns the PDF response. For asynchronous jobs, it records
queued,processing,completedorfailedin DynamoDB and returns a job ID. - The status endpoint returns a short-lived signed S3 download URL only after completion.
Keep the input and output objects private. A deterministic job ID or idempotency key lets retries reuse the same location instead of creating duplicate reports.
Choose synchronous or asynchronous rendering
| Model | Use it when | Response | Main trade-off |
|---|---|---|---|
| Synchronous | Reports are small and normally render within your HTTP timeout. | PDF bytes or a signed URL in one request. | Browser startup, page loading and retries all occupy the request. |
| Asynchronous queue | Reports are slow, image-heavy, unpredictable or arrive in bursts. | Immediate job ID, then status polling or a webhook. | Requires SQS, DynamoDB, retry policy and a dead-letter queue. |
Do not select a mode by average render time alone. Include cold starts, remote image latency, Chromium startup and the longest acceptable user wait. A queue absorbs bursts and prevents a temporary browser failure from becoming a user-facing timeout.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
Package Chromium for Lambda
Use puppeteer-core with a Lambda-compatible @sparticuz/chromium build rather than the full Puppeteer package. The commonly cited Serverless Framework example reports illustrative download sizes of approximately 170 MB on macOS, 282 MB on Linux and 280 MB on Windows; those are package observations, not current AWS quota values. Keep the Chromium package, automation library and Lambda architecture aligned. The example pins x86_64 because that Chromium build ships for that architecture; verify architecture support whenever you change versions.
A minimal dependency set is:
npm install puppeteer-core @sparticuz/chromium @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
Build and deploy the dependencies with the function, a Lambda layer or an image. A layer or image can reduce repeated packaging work, while bundling gives you the most direct version control. Whichever method you use, test the exact artifact in Lambda rather than assuming a local browser binary will run there.
Implement a synchronous renderer
Lambda handler
The following Node.js handler reads a private HTML object, renders it and uploads a PDF. It expects BUCKET and INPUT_PREFIX/OUTPUT_PREFIX environment variables and an authenticated invocation path.
import chromium from "@sparticuz/chromium";
import puppeteer from "puppeteer-core";
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({});
const bucket = process.env.BUCKET;
export const handler = async (event) => {
const jobId = event.requestContext?.http?.pathParameters?.jobId
|| event.pathParameters?.jobId;
if (!jobId || !/^[A-Za-z0-9_-]{1,100}$/.test(jobId)) {
return { statusCode: 400, body: "Invalid job ID" };
}
const object = await s3.send(new GetObjectCommand({
Bucket: bucket,
Key: `reports/${jobId}/index.html`
}));
const html = await object.Body.transformToString();
if (Buffer.byteLength(html, "utf8") > 10 * 1024 * 1024) {
return { statusCode: 413, body: "HTML input is too large" };
}
const browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: { width: 1280, height: 900, deviceScaleFactor: 1 },
executablePath: await chromium.executablePath(),
headless: true
});
try {
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on("request", request => {
const url = request.url();
// Add an allow-list here for production. This example blocks schemes
// that should never be fetched by a report renderer.
if (!/^https?:|^data:|^about:/.test(url)) request.abort();
else request.continue();
});
await page.setContent(html, { waitUntil: "networkidle0", timeout: 45000 });
await page.emulateMediaType("screen");
const pdf = await page.pdf({
format: "A4",
printBackground: true,
margin: { top: "18mm", right: "14mm", bottom: "18mm", left: "14mm" }
});
const key = `reports/${jobId}/report.pdf`;
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: pdf,
ContentType: "application/pdf",
ServerSideEncryption: "AES256"
}));
return {
statusCode: 200,
headers: { "content-type": "application/pdf" },
isBase64Encoded: true,
body: pdf.toString("base64")
};
} finally {
await browser.close();
}
};
For a direct PDF response, base64 encoding is required by the Lambda proxy response format. For larger files, upload to S3 and return a signed URL instead of placing PDF bytes in the HTTP response.
Vercel Route Handler that invokes Lambda
Keep AWS credentials server-side in Vercel environment variables. The route should authenticate the user, validate template and asset references, create the S3 input location and invoke Lambda. A simplified direct-invocation route looks like this:
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
const lambda = new LambdaClient({ region: process.env.AWS_REGION });
export async function POST(request) {
const user = await authenticate(request); // implement your session check
if (!user) return Response.json({ error: "unauthorized" }, { status: 401 });
const { jobId } = await request.json();
if (!/^[A-Za-z0-9_-]{1,100}$/.test(jobId)) {
return Response.json({ error: "invalid jobId" }, { status: 400 });
}
const result = await lambda.send(new InvokeCommand({
FunctionName: process.env.RENDER_FUNCTION_NAME,
InvocationType: "RequestResponse",
Payload: Buffer.from(JSON.stringify({
requestContext: { http: { pathParameters: { jobId } } }
}))
}));
const payload = JSON.parse(Buffer.from(result.Payload).toString());
if (payload.statusCode !== 200) {
return Response.json({ error: "render_failed", detail: payload.body }, { status: 502 });
}
return new Response(Buffer.from(payload.body, "base64"), {
headers: { "content-type": "application/pdf" }
});
}
In a real route, use a presigned S3 upload before invocation. Never accept an arbitrary S3 key, template name or output filename from an unauthenticated request.
Build the queue-backed design
Submission
Vercel creates an idempotency key, writes a DynamoDB item with queued, and sends an SQS message containing only the job ID and S3 keys. The API returns 202 Accepted. The browser can poll /api/reports/{jobId}, or your backend can notify a webhook after completion.
Worker
A worker Lambda consumes SQS, changes the item to processing, loads the private HTML from S3, renders the PDF and writes report.pdf. It then stores completion metadata and a short expiry time for the download URL. If rendering fails, let SQS retry transient failures. Configure a dead-letter queue for messages that exhaust retries, and record the exception without exposing stack traces to end users.
Free tools Windows power users keep installed
One-click scans. No signup required.
Status and download
The status route verifies that the caller owns the job, reads DynamoDB and returns only the state and (when complete) a short-lived presigned S3 URL. Do not proxy private PDF bytes through a publicly cacheable Vercel response unless you deliberately configure cache controls.
Function URL or API Gateway?
A Lambda Function URL is a dedicated HTTPS endpoint for one function. AWS supports AWS_IAM authentication or NONE. A public NONE endpoint needs resource-based permissions allowing invocation; for new Function URLs, AWS states that both lambda:InvokeFunctionUrl and lambda:InvokeFunction permissions are required beginning in October 2025. Put authentication and authorization in front of report generation even if the URL itself is public.
Rank #3
Choose API Gateway when you need multiple routes, centralized request validation, throttling or a broader API-observability model. Choose a Function URL for a single, tightly controlled renderer with simpler routing. In either case, cap input size, validate output names, restrict the browser’s network access and keep credentials out of report HTML.
Security controls that matter
- Authentication: authenticate at Vercel and again at the Lambda boundary when it is directly reachable.
- SSRF protection: allow-list remote hosts or serve assets from your own S3 prefix; block metadata endpoints, private address ranges and unexpected URL schemes.
- Content limits: enforce HTML, asset, page-count and execution-time limits before launching Chromium.
- Isolation: do not place AWS keys, bearer tokens or customer secrets in HTML or query strings.
- Storage: use private S3 objects, server-side encryption and lifecycle rules for temporary inputs and outputs.
- Downloads: issue short-lived signed URLs and verify job ownership on every status request.
- Idempotency: derive object keys from a validated job ID so retries cannot overwrite another customer’s report.
Print fidelity, fonts and assets
Wait for the condition that means the report is actually ready. networkidle0 helps with ordinary pages, but a dashboard that renders after an API call should expose a DOM marker such as data-report-ready="true"; wait for that selector with a timeout. Embed or host fonts where Lambda can reach them, use absolute asset URLs, and set printBackground: true when colored panels are part of the design. Test page breaks, repeating headers, long tables, right-to-left text and images with lazy loading. A screenshot of the first viewport is not a PDF-layout test: inspect page ranges and printed CSS separately.
Performance, reliability and cost planning
- Cold starts: Chromium extraction and browser launch are often the largest fixed delays. Keep the deployment lean and reuse a browser only within one invocation; never assume one browser survives across invocations.
- Concurrency: set reserved or account concurrency deliberately so bursts do not exhaust downstream databases or target websites. SQS lets you tune worker concurrency independently from the public API.
- Timeouts: set page navigation, selector and overall Lambda timeouts separately. A queue job should have a maximum age and a clear failed state.
- Caching: cache immutable templates and assets in S3 or at the application layer, but do not cache personalized reports under a shared key.
- Observability: log job ID, render duration, browser errors and output size; avoid logging report contents or secrets.
- Cost: calculate Lambda duration and memory, S3 storage and requests, SQS, DynamoDB, API Gateway or Function URL usage and Vercel invocations. Exact limits and prices change, so check current AWS and Vercel calculators before launch.
Troubleshooting
“Executable doesn’t exist” or browser launch failure
The Chromium binary is missing, the package architecture does not match the function, or the executable path is wrong. Deploy @sparticuz/chromium with the function, use its executablePath(), and align x86_64/Arm64 choices across all packages.
PDF is blank or missing images
The page was captured before client-side rendering finished, assets require authentication, or URLs are relative to a nonexistent origin. Use absolute URLs, provide required cookies or headers through a controlled server-side fetch, and wait for a ready selector rather than relying only on a fixed sleep.
Function times out
Large images, slow third-party requests or an infinite page script are holding the browser open. Block unnecessary requests, set navigation and selector timeouts, reduce asset size and move the job to SQS when normal reports cannot fit the request window.
Rank #4
“AccessDenied” from S3
Check the Lambda execution role, bucket region, object key and encryption permissions. Keep input and output prefixes explicit and test a single known object before adding dynamic keys.
Public Function URL returns 403
For NONE authentication, verify the resource-based permissions. For new URLs after October 2025, include both lambda:InvokeFunctionUrl and lambda:InvokeFunction permissions, as required by AWS.
Duplicate reports after retry
Your consumer is not idempotent. Persist the job state before rendering, use a deterministic output key, and treat an existing completed object as a successful retry result.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your actual requirement is a clean image or PDF of a public web page rather than a private, data-driven report, ScreenshotNeo provides a single HTTP capture API. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
Install no browser in Lambda; call the API from your server:
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →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}`);
See the ScreenshotNeo API documentation for capture options. Every plan includes the feature set; the Free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.
Best Value
FAQ
Can Puppeteer run in AWS Lambda?
Yes, when the automation library and a Lambda-compatible Chromium build are packaged for the function’s architecture. A desktop Chromium installation is not a portable Lambda deployment.
Should the PDF itself be stored in DynamoDB?
No. Store the binary in S3 and keep only status, keys, ownership and metadata in DynamoDB.
How long should a signed download URL live?
Use the shortest lifetime that fits the user’s download flow, and require a fresh authenticated status request to mint another URL.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Frequently Asked Questions
Can I stream a PDF directly from a Lambda Function URL?
Yes for small synchronous results, using the proxy response format and base64 encoding. For larger or retried jobs, write the object to private S3 and return a signed URL.
What happens when a report contains a private image?
The renderer must receive that asset through an authorized, controlled path—such as an internal fetch or temporary signed object URL. Do not place long-lived credentials in the report HTML.
Is a fixed delay enough to know a page is ready?
No. A readiness selector or application signal is more deterministic; retain a hard timeout as a safety limit.
Quick Recap
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.

