In-process Chromium rendering pool · Node.js
A lightweight in-process rendering pool for Node.js. Runs Chromium once, maintains a fixed set of isolated worker tabs, queues render jobs, and returns screenshots, PDFs, or parsed structured data.
Full reference (mirrors README) — open this file locally or from the repo. Requires Node.js 20+.
Spawning and killing Chromium processes on every render job creates high CPU overhead and memory leaks. Screenpool launches Chromium once and allocates concurrent jobs to a fixed pool of isolated context tabs, recycling contexts on crash/timeout and blanking pages after runs to guarantee bounded resource usage.
about:blank to free up DOM nodes.browserWSEndpoint), HTTP URL (browserURL), or passes an existing Puppeteer Browser directly.Requires Node.js 20+.
npm install screenpool
Note: Screenpool automatically discovers installed Chrome/Chromium on macOS, Linux, Windows, or PATH. If no browser is found, it automatically downloads chrome@stable into ~/.screenpool/browser on first run.
import { ScreenPool } from 'screenpool';
// Create a pool
const pool = new ScreenPool({
browser: 'chrome@stable',
poolSize: 4,
memory: { limitMb: 512 }
});
await pool.start();
// Take a screenshot
const result = await pool.screenshot({
url: 'https://example.com',
viewport: { width: 1200, height: 630 },
format: 'webp',
quality: 80
});
// Do something with the buffer
console.log(result.buffer); // =>
console.log(result.contentType); // => 'image/webp'
// Stop the pool gracefully
await pool.stop();
Screenpool supports several ways to resolve or connect to a browser. Define these in the top-level configuration options:
| Method | Description | Example Config |
|---|---|---|
executablePath |
System file path to a Chromium/Chrome binary. | { executablePath: '/usr/bin/chromium' } |
browser |
Browser shorthand string or configuration resolved via @puppeteer/browsers cache. | { browser: 'chrome@stable' } |
browserWSEndpoint |
Connects to an already running browser using its WebSocket debugging endpoint URL. | { browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser/...' } |
browserURL |
Connects to an already running browser using its local debugging port URL. | { browserURL: 'http://localhost:9222' } |
browserInstance |
Directly pass an existing Puppeteer Browser instance. Lifecycle is managed by user. |
{ browserInstance: existingBrowser } |
new ScreenPool(config)| Option | Default | Description |
|---|---|---|
poolSize | 4 | Number of concurrent worker pages/tabs to maintain. |
maxQueueSize | 100 | Maximum number of pending render jobs to queue before failing with QueueOverflow. |
jobTimeout | 15000 | Timeout in ms for single render jobs. |
workerRestartAfterJobs | 500 | Recycle worker tab/context after this many completed jobs (0 disables). |
allowLocalhost | false | Whether to allow renders from localhost URLs. |
allowPrivateNetworks | false | Whether to allow rendering URLs resolving to private subnets. |
allowFileProtocol | false | Whether to allow local file paths (file://). |
defaultViewport | { width: 1280, height: 720 } | Default viewport settings applied to worker pages on start/recycle. |
memory.limitMb | — | Upper limit on browser RSS usage in MB. Blocks jobs if exceeded. |
memory.v8HeapMb | — | Limits Chromium V8 engine heap size via launch arguments. |
async pool.start()
Launches/connects the browser process and initializes the fixed worker page pool.
async pool.stop()
Closes all worker contexts and gracefully terminates the browser process (if started locally) or disconnects (if connected remotely).
async pool.screenshot(options)
Captures a screenshot of the specified URL or raw HTML template content. Returns RenderResult.
const res = await pool.screenshot({
url: 'https://example.com',
format: 'png', // 'png' | 'jpeg' | 'webp'
quality: 90, // for jpeg/webp
fullPage: true, // captures entire scrollable page height
darkMode: true, // injects color preference
userAgent: 'my-custom-ua',
headers: { 'Authorization': 'Bearer ...' },
cookies: [{ name: 'session', value: '...' }]
});
async pool.pdf(options)
Generates a PDF from a URL or raw HTML content. Returns RenderResult.
const res = await pool.pdf({
url: 'https://example.com',
pdf: {
format: 'A4',
landscape: false,
printBackground: true,
margin: { top: '1cm', bottom: '1cm' }
}
});
async pool.extract(options)
Extracts structured data from HTML pages rendered in the browser using Pipsel DSL.
const result = await pool.extract({
url: 'https://news.ycombinator.com',
rules: `
stories[]: "tr.athing" {
id: self | attr("id") | int
title: "span.titleline > a" | text
}
`
});
console.log(result.data.stories); // => [{ id: 40912, title: "..." }]
pool.stats()
Returns statistics regarding completed jobs, active queues, browser restarts, and memory utilization.
async pool.getPageStats()
Count of total open tabs and contexts across default context vs. worker contexts (primarily used for memory leak analysis).
Screenpool provides a strict, observation-based browser action engine and session recorder for interactive multi-step flows, popup windows, OAuth logins, and web recording.
import { ScreenPool } from 'screenpool';
const pool = new ScreenPool({ poolSize: 2 });
await pool.start();
// 1. Create an isolated session
const session = await pool.sessions.create({
pages: { maxPages: 5, onPopup: 'register', onActivePageClosed: 'activate-opener' },
});
// 2. Start recording
const recording = await session.record.start({ preset: 'debug', screenshots: 'each-action' });
// 3. Navigate and Observe
await session.goto('https://example.com');
const obs = await session.observe({ screenshot: true, elements: true });
// 4. Execute Action Sequence
const actResult = await session.act({
observationId: obs.id,
actions: [
{
type: 'click',
target: { by: 'role', role: 'button', name: 'Open Auth' },
expect: { page: { event: 'popup', alias: 'oauth-login', activate: true } }
}
]
});
// 5. Stop Recording & Close
const manifest = await recording.stop();
await session.close();
await pool.stop();
Chromium renders page links on your server. To prevent users from requesting internal resources, Screenpool includes built-in SSRF checks blocking loopback interfaces (localhost, 127.0.0.1), private IPs (10.0.0.0/8, 192.168.0.0/16), link-local subnets, and AWS/cloud metadata services (169.254.169.254).
To allow local testing or developer instances, enable access explicitly during initialization:
const pool = new ScreenPool({
allowLocalhost: true, // Allow localhost and loopback resolving
allowPrivateNetworks: true, // Allow access to standard private ranges (192.168.X.X, etc.)
allowFileProtocol: true // Allow opening file:// paths on the host system
});
Screenpool packages a Hono-based HTTP server to daemonize screenshotting and rendering services.
import { ScreenPool } from 'screenpool';
import { createScreenPoolServer } from 'screenpool/http';
const pool = new ScreenPool({ browser: 'chrome@stable' });
await pool.start();
const server = createScreenPoolServer(pool, { port: 3000, host: '0.0.0.0' });
await server.listen();
| Endpoint | Method | Description |
|---|---|---|
/screenshot | POST | Render a screenshot of URL or raw html. Returns image payload. |
/pdf | POST | Render a PDF. Returns application/pdf payload. |
/html-to-image | POST | Render raw html payload. Requires html in request body. |
/html-to-pdf | POST | Render raw html to PDF. Requires html in request body. |
/extract | POST | Extract structured JSON using Pipsel rules. |
/stats | GET | Returns current server pool statistics JSON. |
/health | GET | Checks browser connection state and system memory status. |
Screenpool includes built-in Model Context Protocol (MCP) server capabilities over stdio. This enables AI coding assistants (Claude Code, Cursor, VS Code MCP clients, Antigravity, etc.) to perform web rendering, screenshot capture, PDF generation, HTML extraction, and health monitoring.
# Run MCP server via npx
npx screenpool mcp
# Run using installed binary
screenpool mcp
# Standalone binary
screenpool-mcp
| Tool Name | Description |
|---|---|
screenpool_screenshot | Capture web page screenshot (png, jpeg, webp, fullPage, viewport, selector). |
screenpool_pdf | Render web page as PDF (A4, Letter, landscape, margins). |
screenpool_html | Extract client-side rendered HTML after JS execution (with truncation support). |
screenpool_metadata | Extract page title, meta description, and canonical URL. |
screenpool_session_create | Create an isolated browser session with multi-page tracking. |
screenpool_session_pages | List managed pages in session and active/main status. |
screenpool_session_close | Close active browser session and release isolated context. |
screenpool_observe | Capture page observation state including interactive element IDs and compact DOM. |
screenpool_act | Execute strict, verifiable browser actions (click, fill, press, select, scroll, wait, page actions). |
screenpool_run | Stateless browser action run in a temporary session. |
screenpool_record_start | Start session recording (events jsonl, action screenshots, and video). |
screenpool_record_stop | Stop session recording and return recording manifest. |
screenpool_record_get | Get active session recording status. |
screenpool_health | View worker pool health status, active jobs, uptime, and queue length. |
screenpool_capabilities | Inspect supported formats, tool list, and version information. |
screenpool_help | Structured documentation, parameter guides, diagnostics presets, and examples. |
{
"mcpServers": {
"screenpool": {
"command": "npx",
"args": [
"-y",
"screenpool",
"mcp",
"--pool-size",
"3",
"--timeout",
"30000"
]
}
}
}
Command line interface tools for quick scripts, daemons, and MCP server:
# Start MCP Server for AI tools over stdio
screenpool mcp --pool-size 3 --timeout 30000
# Take a screenshot and write to out.png
screenpool screenshot https://example.com --out out.png --width 1280 --height 720
# Connect to an existing browser via remote debugging and take a screenshot
screenpool screenshot https://example.com --browser-url http://localhost:9222 --out out.png
# Render a PDF using a custom Chromium binary path
screenpool pdf https://example.com --executable-path /usr/bin/chromium --out page.pdf
# Start HTTP Server daemon on port 3000
screenpool server --port 3000 --pool-size 4
# Start server daemon with interactive web UI panel
screenpool ui --port 3000
┌────────────────────────────────────────────────────────────────┐ │ ScreenPool │ ├────────────────────┬────────────────────┬──────────────────────┤ │ JobQueue │ HealthMonitor │ WorkerPool │ │ │ │ ┌────────────────┐ │ │ [Job1, Job2...] │ (Memory / RSS) │ │ ScreenWorker 0 │ │ │ │ │ │ ScreenWorker 1 │ │ │ │ │ │ ScreenWorker 2 │ │ │ │ │ │ ScreenWorker 3 │ │ │ │ │ └────────────────┘ │ ├────────────────────┴────────────────────┴──────────────────────┤ │ BrowserManager │ ├────────────────────────────────────────────────────────────────┤ │ Puppeteer (Connect/Launch Contexts) │ ├────────────────────────────────────────────────────────────────┤ │ Chromium Browser Process │ └────────────────────────────────────────────────────────────────┘
The codebase uses Vitest for integration and unit testing.
npm run test
npm run test:watch
Build output files using tsup before distributing: npm run build.