Capturing screenshots of web pages programmatically can be incredibly useful for generating previews, creating image-based reports, and more. In this guide, we’ll build a Next.js API route that takes a URL and generates a PNG screenshot. Our setup uses Puppeteer and chrome-aws-lambda to leverage a headless Chrome browser, making it versatile and production-ready.
We’ll start by setting up a new Next.js project and walking through the code step-by-step to understand how the API captures screenshots.
Prerequisites
- Setting up the Next.js app
- Configuring the API route with Puppeteer
- Creating the React component for the capture interface
- Explanation of local vs. deployment configurations for Puppeteer
Getting Started with a New Next.js Project
- Create a new Next.js app:
npx create-next-app@latest capture-image-app cd capture-image-app
- Install the necessary dependencies:
npm install puppeteer puppeteer-core chrome-aws-lambda busboy
Step 2: Create the API Route to Generate Screenshots
Now, we’ll set up an API endpoint to capture and return screenshots based on a provided URL.
In the pages/api folder, create a new file named generate-png.ts and add this code:
import { NextApiRequest, NextApiResponse } from "next"; import busboy, { Busboy } from "busboy"; // Use busboy for multipart parsing import chromium from "chrome-aws-lambda"; import puppeteerCore from "puppeteer-core"; // Import puppeteer-core directly import puppeteer from "puppeteer"; // Import puppeteer directly // Conditional import for Puppeteer based on the environment const puppeteerModule = process.env.NODE_ENV === "production" ? puppeteerCore : puppeteer; export const config = { api: { bodyParser: false, // Disable default body parsing to handle raw binary data (Blob) }, }; const delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms)); export default async function handler( req: NextApiRequest, res: NextApiResponse ): Promise<void> { try { if (req.method === "POST") { const bb: Busboy = busboy({ headers: req.headers }); let width: number = 1920; // Default width let height: number = 0; // Default height let delayTime: number = 6000; const buffers: Buffer[] = []; bb.on("file", (_name: string, file: NodeJS.ReadableStream) => { file.on("data", (data: Buffer) => buffers.push(data)); }); bb.on("field", (name: string, value: string) => { if (name === "width") width = parseInt(value, 10) || 1920; if (name === "height") height = parseInt(value, 10) || 0; if (name === "delay") delayTime = parseInt(value, 10) || 6000; }); bb.on("finish", async () => { const blobBuffer: Buffer = Buffer.concat(buffers); const htmlContent: string = blobBuffer.toString("utf-8"); const browser = await puppeteerModule.launch({ args: ["--start-maximized"], executablePath: process.env.NODE_ENV === "production" ? await chromium.executablePath || "/usr/bin/chromium-browser" : undefined, // No custom executable path needed for local headless: true, }); const page = await browser.newPage(); // Load the HTML content directly await page.setContent(htmlContent, { waitUntil: "networkidle0" }); //@ts-expect-error todo const bodyHeight = await page.evaluate(() => { return document.body.scrollHeight; // Get the full scrollable height of the body }); await page.setViewport({ width: Number(width), height: height || bodyHeight, // Use the provided height or fallback to the full body height deviceScaleFactor: 2, }); await delay(delayTime); const screenshotBuffer = await page.screenshot({ fullPage: !height, type: "png", omitBackground: false, }); await browser.close(); res.setHeader("Content-Type", "image/png"); res.setHeader( "Content-Disposition", "attachment; filename=screenshot.png" ); res.status(200).end(screenshotBuffer); }); req.pipe(bb); // Pipe the request stream to busboy } else { res.setHeader("Allow", ["POST"]); res.status(405).end(`Method ${req.method} Not Allowed`); } } catch (error) { console.error("ERROR", error); res.status(500).end("Internal Server Error"); } } </void></void>
*Explanation: Choosing Puppeteer for Local vs. Production Environments
*
In this code, we’ve set up a dynamic import for puppeteer:
Local Development: If NODE_ENV is not production, it uses puppeteer, which is simpler to set up and doesn’t require chrome-aws-lambda.
Production: For serverless deployments, the environment will detect NODE_ENV as production and load puppeteer-core along with chrome-aws-lambda, which allows it to work in AWS Lambda and other similar environments. In this setup, chrome-aws-lambda provides the correct Chromium path, ensuring compatibility with serverless providers.
Step 3: Create a Simple React Component for the UI
Here, we’ll create a straightforward form that lets users input values for the webpage capture. This form will trigger the generate function to capture and download the screenshot in PDF format.
import { useState } from "react"; export default function ScreenCaptureComponent() { const [isProcessing, setProcessing] = useState(false); const [width, setWidth] = useState<string>("1920"); const [height, setHeight] = useState<string>("1000"); const [delay, setDelay] = useState<string>("6000"); // Function to clone HTML and prepare for capture function takeScreenshot() { const clonedElement = document.body.cloneNode(true) as HTMLElement; const blob = new Blob([clonedElement.outerHTML], { type: "text/html" }); return blob; } // Function to capture screenshot by sending cloned HTML to API async function generateCapture() { setProcessing(true); const htmlBlob = takeScreenshot(); if (!htmlBlob) { setProcessing(false); return; } try { const formData = new FormData(); formData.append("file", htmlBlob); formData.append("width", width); formData.append("height", height); formData.append("delay", delay); const response = await fetch("/api/generate-png", { method: "POST", body: formData, }); if (!response.ok) throw new Error("Capture failed"); const blob = await response.blob(); const downloadUrl = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = downloadUrl; link.download = "capture.png"; link.click(); URL.revokeObjectURL(downloadUrl); } catch (error) { console.error("Failed to capture screenshot", error); } finally { setProcessing(false); } } return ( <div style="{{" maxwidth: margin: auto padding: backgroundcolor: borderradius: width: boxshadow: rgba> <h2 style="{{" fontsize: fontweight: textalign: marginbottom:> Webpage Screenshot Capture </h2> <form onsubmit="{(e)"> { e.preventDefault(); generateCapture(); }} style={{ display: "flex", flexDirection: "column", alignItems: "center", marginBottom: "16px", }} > <label style="{{" marginbottom: fontweight: htmlfor="width"> Width (px) </label> <select id="width" value="{width}" onchange="{(e)"> setWidth(e.target.value)} style={{ width: "100%", padding: "8px", marginBottom: "16px", borderRadius: "4px", border: "1px solid #ccc", outline: "none", }} > <option value="1920">1920 (Full HD)</option> <option value="1366">1366 (Laptop)</option> <option value="1280">1280 (Desktop)</option> <option value="1024">1024 (Tablet Landscape)</option> <option value="768">768 (Tablet Portrait)</option> <option value="375">375 (Mobile)</option> </select> <label style="{{" marginbottom: fontweight: htmlfor="height"> Height (px) </label> <input type="number" id="height" value="{height}" onchange="{(e)"> setHeight(e.target.value)} required style={{ width: "100%", padding: "8px", marginBottom: "16px", borderRadius: "4px", border: "1px solid #ccc", outline: "none", }} /> <label style="{{" marginbottom: fontweight: htmlfor="delay"> Delay (ms) </label> <input type="number" id="delay" value="{delay}" onchange="{(e)"> setDelay(e.target.value)} required style={{ width: "100%", padding: "8px", marginBottom: "16px", borderRadius: "4px", border: "1px solid #ccc", outline: "none", }} /> <button type="submit" disabled style="{{" padding: color: borderradius: transition: backgroundcolor: isprocessing : cursor:> {isProcessing ? "Capturing..." : "Capture Screenshot"} </button> </form> {/* Example HTML Element to Capture */} <div id="capture-area" style="{{" display:> <h3 style="{{" fontsize: fontweight:> Content to Capture </h3> <p>This is an example of the HTML content that will be captured.</p> </div> </div> ); } </string></string></string>
Conclusion
This tutorial covers setting up a webpage capture tool in Next.js, handling screenshots with Puppeteer, and creating an interactive frontend component. Remember to use puppeteer locally and switch to puppeteer-core in production to reduce bundle size and optimize for serverless environments. Happy coding!
The above is the detailed content of How to Capture Web Page Screenshots with Next.js and Puppeteer. For more information, please follow other related articles on the PHP Chinese website!

JavaScriptusestwotypesofcomments:single-line(//)andmulti-line(//).1)Use//forquicknotesorsingle-lineexplanations.2)Use//forlongerexplanationsorcommentingoutblocksofcode.Commentsshouldexplainthe'why',notthe'what',andbeplacedabovetherelevantcodeforclari

The main difference between Python and JavaScript is the type system and application scenarios. 1. Python uses dynamic types, suitable for scientific computing and data analysis. 2. JavaScript adopts weak types and is widely used in front-end and full-stack development. The two have their own advantages in asynchronous programming and performance optimization, and should be decided according to project requirements when choosing.

Whether to choose Python or JavaScript depends on the project type: 1) Choose Python for data science and automation tasks; 2) Choose JavaScript for front-end and full-stack development. Python is favored for its powerful library in data processing and automation, while JavaScript is indispensable for its advantages in web interaction and full-stack development.

Python and JavaScript each have their own advantages, and the choice depends on project needs and personal preferences. 1. Python is easy to learn, with concise syntax, suitable for data science and back-end development, but has a slow execution speed. 2. JavaScript is everywhere in front-end development and has strong asynchronous programming capabilities. Node.js makes it suitable for full-stack development, but the syntax may be complex and error-prone.

JavaScriptisnotbuiltonCorC ;it'saninterpretedlanguagethatrunsonenginesoftenwritteninC .1)JavaScriptwasdesignedasalightweight,interpretedlanguageforwebbrowsers.2)EnginesevolvedfromsimpleinterpreterstoJITcompilers,typicallyinC ,improvingperformance.

JavaScript can be used for front-end and back-end development. The front-end enhances the user experience through DOM operations, and the back-end handles server tasks through Node.js. 1. Front-end example: Change the content of the web page text. 2. Backend example: Create a Node.js server.

Choosing Python or JavaScript should be based on career development, learning curve and ecosystem: 1) Career development: Python is suitable for data science and back-end development, while JavaScript is suitable for front-end and full-stack development. 2) Learning curve: Python syntax is concise and suitable for beginners; JavaScript syntax is flexible. 3) Ecosystem: Python has rich scientific computing libraries, and JavaScript has a powerful front-end framework.

The power of the JavaScript framework lies in simplifying development, improving user experience and application performance. When choosing a framework, consider: 1. Project size and complexity, 2. Team experience, 3. Ecosystem and community support.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

PhpStorm Mac version
The latest (2018.2.1) professional PHP integrated development tool

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

SublimeText3 Chinese version
Chinese version, very easy to use

SecLists
SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.

Dreamweaver Mac version
Visual web development tools
