Browser Rendering
Browser Rendering
Section titled “Browser Rendering”@workkit/browser is a thin primitive over Cloudflare Browser Rendering — session/page lifecycle, font loading, and normalized errors. It’s the shared base for @workkit/pdf and any future screenshot/OG packages.
Install
Section titled “Install”bun add @workkit/browser @cloudflare/puppeteer@cloudflare/puppeteer is an optional peer — bring your own version.
When you need it: anything that touches Puppeteer’s scripting API — page.pdf, page.screenshot, page.evaluate, page.click, page.waitForSelector. That’s the quick-start below and most real workloads.
When you can skip it: the raw BROWSER binding now exposes .launch() natively, returning a minimal session that can open a page and dump final HTML but cannot script it. browser() automatically takes this path when options.puppeteer is not supplied. Useful only if you’re bundle-size-constrained and don’t need the scripting surface.
Quick start
Section titled “Quick start”import puppeteer from "@cloudflare/puppeteer";import { browser, withPage, loadFonts } from "@workkit/browser";
export default { async fetch(req: Request, env: Env) { const session = await browser(env.BROWSER, { puppeteer });
const bytes = await withPage(session, async (page) => { await (page as any).setContent("<h1>Hello</h1>", { waitUntil: "networkidle2" }); await loadFonts(page, [ { family: "Inter", url: "https://fonts.example.com/Inter.woff2" }, ]); return (page as any).pdf({ format: "A4" }); });
return new Response(bytes, { headers: { "content-type": "application/pdf" } }); },};browser(binding, options?)
Section titled “browser(binding, options?)”Acquires a Cloudflare Browser Rendering session.
binding—env.BROWSERoptions.puppeteer—@cloudflare/puppeteerinstance (recommended)options.keepAlive—number(ms) — keep session alive between renders. Off by default; opt-in carries a state-leak risk.options.launch— extra options forwarded topuppeteer.launch
withPage(session, fn, options?)
Section titled “withPage(session, fn, options?)”Runs fn(page) with guaranteed page.close() on success, throw, or abort.
options.js—boolean(defaultfalse). Untrusted HTML can execute scripts whentrue.options.timeoutMs— per-page operation timeout. Default15000. Override globally viaWORKKIT_BROWSER_TIMEOUT_MS.options.signal—AbortSignal. On abort the page closes and the promise rejects with the abort reason.options.autoDismissDialogs— auto-dismissalert/confirm/prompt/beforeunload. Defaulttrue.
loadFonts(page, fonts, options?)
Section titled “loadFonts(page, fonts, options?)”Injects @font-face declarations and waits for them to be ready.
fonts—Array<{ family, url, weight?, style?, display? }>. URLs must be HTTPS.options.timeoutMs— default5000.options.verifyAvailable— defaulttrue(no silent fallback). ThrowsFontLoadErrorif the registered font isn’t actually available after load.
Security defaults
Section titled “Security defaults”- JS off by default. Untrusted HTML cannot execute scripts unless you opt in with
js: true. - No URL navigation helper. Use
page.setContent(html)for trusted templated rendering. If you need to navigate to URLs, write your own SSRF guard at the call site — we don’t ship one because the right policy is consumer-specific. keepAliveleaks state. Cookies, storage, and JS state persist when sharing a session. Only use for trusted, non-PII workloads.- Font URLs are HTTPS-only.
loadFontsrejectshttp://,data:,file://and characters that could break out of theurl("…")token (quotes, parens, control chars, backslash). Same guard for family names. - Dialogs auto-dismissed by default. Prevents stuck pages on
beforeunload/confirmfrom untrusted templates.
Errors
Section titled “Errors”All failures normalize through @workkit/errors:
| Condition | Error |
|---|---|
Browser binding 429 (with Retry-After) |
RateLimitError (carries retryAfterMs; case-insensitive header lookup) |
| Browser binding 502/503/504 | ServiceUnavailableError |
| Operation timeout | TimeoutError |
| Font registered but unavailable | FontLoadError (extends ValidationError) |
| Non-HTTPS or unsafe font URL | FontLoadError |
Cost monitoring
Section titled “Cost monitoring”Browser Rendering is priced per session. Recommended pattern:
- Wire
@workkit/ratelimitper user before calling any render function. - Increment an Analytics Engine counter on every session acquisition.
- Alert at 50,000 sessions / month as a sanity ceiling.
- If you cross the threshold consistently, evaluate
@react-pdf/rendererfor templated content where Browser Rendering’s full layout engine isn’t needed.
See also
Section titled “See also”- PDF Rendering —
@workkit/pdfbuilds on@workkit/browser. - Cloudflare Browser Rendering docs