TypeScript · Zero deps · Self-hosted

Website Screenshots,
On Demand

One function call captures any URL with a headless browser, caches it on disk, and returns a base64 data URL — so your app never re-launches a browser for the same page twice.

npm i @npmforge/snapforgeSee live demo
vercel.com
www.velocityui.com
www.mozilla.org

Self-hosted

Run the capture service on your own infra. Your screenshots never leave your servers.

Disk-cached

First capture stored on disk. Repeat requests within the TTL window skip the headless browser entirely.

TypeScript-first

Fully typed SnapshotResult keeps your image handlers compile-safe from request to render.

Full-page capture

Configure viewport width, height, and fullPage mode per request or set sensible project defaults.

TTL control

Set a default cache duration on the client and override it per-request for fine-grained refresh control.

Zero runtime deps

The SDK ships with no runtime dependencies — just the native fetch API and Node's Buffer.

The Problem

Headless browsers are expensive to spin up. Without a capture service, every screenshot request cold-starts Puppeteer, burns memory, and produces no cache.

Without Snapforge
// Repetitive: manual Puppeteer setup on every call
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900 });
await page.goto(url, { waitUntil: "networkidle2" });
const buf = await page.screenshot({ fullPage: true });
await browser.close();

// No caching — same URL re-launches a full browser
const dataUrl = `data:image/png;base64,${buf.toString("base64")}`;
With Snapforge
import { createSnapforgeClient } from "@npmforge/snapforge";

const client = createSnapforgeClient({ baseUrl, apiKey });

const snapshot = await client.getSnapshot({
  url: "https://example.com",
  fullPage: true,
  width: 1440,
});

// snapshot.dataUrl ready to embed in <img src>
// snapshot.cached tells you if it was served from disk
if (snapshot.cached) scheduleRefresh(snapshot.expiresAt);

Consistent output, every time

Every getSnapshot() call returns the same typed object — whether the image was freshly captured or served from cache.

FieldTypeExampleDescription
dataUrlstring"data:image/png;base64,..."Base64-encoded screenshot ready to embed in an img src.
cachedbooleantrueWhether the response was served from disk cache.
capturedAtstring"2024-01-15T10:30:00Z"ISO timestamp of when the screenshot was taken.
expiresAtstring"2024-01-15T10:35:00Z"ISO timestamp of when the cache entry expires.
ttlSecondsnumber300Cache duration in seconds that was applied.
mimeTypestring"image/png"MIME type of the returned image.
sourceUrlstring"https://example.com"The original URL that was captured.
bytesUint8ArrayUint8Array(89203)Raw image bytes for custom processing or storage.

Works out of the box with

Next.js RSCExpressHonoNode.js fetchReact Server ComponentsEdge runtime

Live Playground

Each card below is a real getSnapshot() call rendered server-side. Badges show whether the image was served from cache or freshly captured.

Vercel

Loading…

Fetching snapshot…

Velocity UI

Loading…

Fetching snapshot…

Mozilla

Loading…

Fetching snapshot…

Get started in seconds

Deploy the capture service and call getSnapshot() from any Node environment.

npm i @npmforge/snapforge