Free tools Windows power users keep installed
One-click scans. No signup required.
Render the EJS view through an Express route, open that route in Puppeteer, wait until the content you need is ready, and call page.screenshot(). This captures the HTML Express actually serves, including the template data and client-side rendering that a visitor would see.
How the screenshot flow works
EJS turns a template and its local values into HTML. Express makes that rendered HTML available through a route, and Puppeteer controls a browser to visit the route and capture its pixels. Keeping those steps separate makes it easier to debug template output independently from browser capture.
- Configure Express to use EJS and point it at the directory containing your views.
- Create a route that calls
res.render()with a known template name and the data that template needs. - Start the Express server and wait until it is listening.
- Launch Puppeteer, set the browser viewport, navigate to the route, and wait for the relevant content.
- Save the screenshot and close the browser, including when navigation or capture throws an error.
Install EJS and Puppeteer
In an existing Node.js project, install Express, EJS, and Puppeteer:
npm install express ejs puppeteer
The Puppeteer documentation version 25.12.0 describes npm i puppeteer as the standard installation and says the package downloads a recent Chrome for Testing and a compatible chrome-headless-shell. The documented download estimates are approximately 170 MB on macOS, 282 MB on Linux, and 280 MB on Windows; actual disk requirements can vary by environment.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →#1 Best Overall
If package installation scripts are disabled, Puppeteer may not download its browser. The documented manual installation command is npx puppeteer browsers install. If you choose puppeteer-core instead, it does not download Chrome: you are responsible for providing an installed browser or a remote browser connection and configuring Puppeteer to use it.
Configure Express to render an EJS view
Express needs a view directory and a view engine. With the conventional views directory at the project root, a minimal server can look like this:
const express = require('express');
const app = express();
app.set('views', './views');
app.set('view engine', 'ejs');
app.get('/preview', (req, res) => {
res.render('report', {
title: 'Quarterly report',
rows: [
{ label: 'Revenue', value: '$24,000' },
{ label: 'Expenses', value: '$9,000' },
],
});
});
app.listen(3000, () => {
console.log('Preview app listening on http://localhost:3000');
});
Save this as server.js. Create views/report.ejs:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= title %></title>
<style>
body { font: 16px system-ui, sans-serif; margin: 0; padding: 32px; }
main { max-width: 720px; margin: auto; }
li { padding: 8px 0; }
</style>
</head>
<body>
<main>
<h1><%= title %></h1>
<ul>
<% for (const row of rows) { %>
<li><strong><%= row.label %>:</strong> <%= row.value %></li>
<% } %>
</ul>
</main>
</body>
</html>
In the template, <%= value %> emits HTML-escaped output. EJS’s <%- value %> emits output without escaping, which is useful for trusted markup such as an include but unsafe for untrusted values unless they have been appropriately sanitized. Validate route inputs and keep the template name controlled by your application. EJS warns that rendering with unchecked user input leaves the application responsible for the result; do not give users unrestricted access to rendering.
Capture the Express route with Puppeteer
Run the server in one terminal with node server.js. Once it reports that it is listening, run this capture script from the project directory as screenshot.js:
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 & 11const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 900 });
await page.goto('http://localhost:3000/preview', {
waitUntil: 'networkidle2',
});
await page.screenshot({ path: 'preview.png', fullPage: true });
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Start it with node screenshot.js. The route must be reachable when page.goto() runs; launching Puppeteer does not start your Express server. Setting the viewport before navigation matters when responsive layout affects the result. Puppeteer notes that changing the viewport can resize the page and may cause a reload in some cases.
networkidle2 is the wait condition shown in Puppeteer’s screenshot guide, not a guarantee that every application has finished rendering. Pages with persistent requests or delayed client-side work may need a more specific readiness check.
Choose what to capture
Viewport or full page
By default, a screenshot covers the current viewport. Set fullPage: true to capture the full document height, as in the example. Use viewport capture when the desired image is a browser-window view; use full-page capture for a report or page that should appear in one tall image.
A single element
Wait for the target element, then capture its element handle. Puppeteer scrolls an element into view by default when taking an element screenshot.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsconst card = await page.waitForSelector('.report-card');
if (!card) throw new Error('Report card was not found');
await card.screenshot({ path: 'report-card.png' });
Use a selector that identifies the intended component uniquely. If a page renders the component asynchronously, waiting for its selector is generally a more meaningful condition than assuming a fixed delay is enough.
File format, clipping, and background
The screenshot path determines where the image is written; relative paths resolve from the process’s current working directory. Puppeteer uses the file extension to infer the screenshot type, and PNG is the default. The type option can select an image format. For a region of the page, provide a clip rectangle; to omit the default white background and allow transparency, use omitBackground: true.
Rank #3
await page.screenshot({
path: 'transparent.png',
type: 'png',
omitBackground: true,
});
Render HTML directly instead of navigating to Express
If you already have rendered HTML and do not need to exercise the HTTP route, Puppeteer supports setting page content directly:
const html = '<main><h1>Preview</h1></main>';
await page.setContent(html);
await page.screenshot({ path: 'preview.png' });
This route is useful for an isolated markup capture, but it skips the real Express request path. For screenshots of the page your application serves, navigate to its route so the browser also loads the page’s linked stylesheets, scripts, and assets in their normal context.
Wait for the right readiness condition
The correct wait depends on how the EJS page works. A page whose HTML is complete in the initial response may need only navigation to finish. If browser JavaScript fills in a chart, fetches data, or reveals a component later, wait for the application-specific signal that means the screenshot content is ready.
- For a component that appears after rendering, use
page.waitForSelector('.report-card'). - For a fixed, known delay in a controlled workflow, use a delay sparingly; it can be either unnecessarily slow or too short under load.
- For pages with ongoing network requests, do not assume
networkidle2will represent visual completion. Prefer a selector or other application-specific readiness condition.
Waiting for the main selector before capture is also helpful when the view can legitimately return an empty state. Choose a selector that exists in both expected states if you need to capture either one.
Common failures and fixes
Navigation fails with a connection error
Cause: Express is not running, has not started listening yet, or the script is using the wrong port or route. Fix: start the server first, confirm the URL in a browser, then make the URL passed to page.goto() match the server’s port and route.
Puppeteer reports that Chrome is missing
Cause: browser download scripts may have been blocked, or a separately managed browser was not configured. Fix: with Puppeteer, run npx puppeteer browsers install. With puppeteer-core, install or connect to a compatible browser and configure its executable path or channel.
The screenshot is blank or missing template data
Cause: the route may be returning an error, rendering a different view, or waiting on client-side content that has not appeared. Fix: open the route directly in a browser and inspect the rendered page; check the view filename, Express locals, and readiness selector before changing the screenshot options.
The page is cut off
Cause: the default screenshot captures only the viewport. Fix: set fullPage: true for the entire document, or use an element screenshot when only one component is needed.
Layout differs from the expected design
Cause: capture dimensions or device pixel behavior differ from the intended browser view, or viewport was set after navigation. Fix: set the viewport before page.goto(), use dimensions appropriate to the design, and ensure the page has finished loading its fonts, styles, and relevant content before capture.
Capture hangs or times out
Cause: navigation may be waiting on a persistent request or the server may not be responding. Fix: verify the route is responsive and replace a broad network-idle wait with a specific selector or application readiness condition when the page keeps network activity open.
Reliability, performance, and cost considerations
Launching a browser for every image is straightforward, but adds browser startup and shutdown work. For one-off captures, a single launch with cleanup in finally is simple and robust. In a service that takes many screenshots, browser lifecycle, concurrency, timeouts, and memory use become operational concerns: avoid unbounded simultaneous pages, close pages and browsers when work completes, and test your chosen limits in the environment where the service runs.
Puppeteer’s browser download is a significant installation component, with the version-specific estimates described above. If deployment policy or image size makes bundling that browser unsuitable, puppeteer-core can use a browser managed separately, but that shifts browser compatibility and availability responsibility to your deployment.
For failures, distinguish an app-render error from a capture error. Test the Express route on its own first, then add Puppeteer. Log the route URL and the phase that failed, and ensure cleanup runs even when navigation or screenshot writing throws. This narrows diagnosis without turning transient or incomplete pages into apparently successful images.
Or skip the browser setup
If you want an API to capture a URL rather than install and manage a local browser, ScreenshotNeo takes a screenshot in one GET request. Its API can return PNG, JPEG, WebP, or PDF; the request below saves the result as WebP. See the ScreenshotNeo API documentation for request options.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-site.example/preview -o shot.webp
ScreenshotNeo removes supported cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
Frequently asked questions
Can Puppeteer take a screenshot of an EJS file without Express?
Yes, if you render the template to HTML yourself and provide that markup to the page. For an application using Express, navigating to its rendered route is the more representative way to capture the delivered page.
Does EJS itself take the screenshot?
No. EJS produces HTML from a template and locals. Puppeteer controls the browser and captures the rendered page.
Recommended Free Tools
Where does Puppeteer save a relative screenshot path?
Relative screenshot paths resolve from the Node.js process’s current working directory, not automatically from the directory containing the EJS view.
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.




