Skip to content
OTFotf
All posts

Cloudflare browser rendering for SaaS without a browser fleet

D
DaveAuthor
7 min read
Dex, Luna, and Byte review a browser-rendering request as a floating unit returns screenshot and PDF pages; Nova hovers at the right edge, and the usage meter sits near the lower center.

A screenshot or PDF endpoint looks small in a product spec: accept a report, render it, return a file. The hidden work is the browser fleet behind that endpoint—launching processes, managing concurrency, waiting for pages, collecting failures, and keeping resource use inside a budget.

Cloudflare Browser Run gives a SaaS team a hosted browser service for that rendering step. The practical question is how to expose it as a product feature without turning every request into an unmanaged browser session. Start with the simplest API that fits the job, put your own application controls around it, and measure actual browser time before estimating unit economics.

Choose the rendering interface by task

Browser Run has two broad ways to render. Quick Actions handle one-shot jobs such as screenshots, PDFs, page content, and extraction. You can call them over REST or through a Workers binding. For a SaaS report or social preview, that is usually the first option to evaluate: one request in, one artifact out, with no need to keep a browser connection open while your application performs a sequence of clicks.

Use Browser Sessions when the job needs direct browser control: navigate through several screens, wait for application state, interact with controls, or reuse a session across multiple steps. Cloudflare supports browser automation through Puppeteer, Playwright, CDP, and related tooling. That control is useful, but it brings a second resource dimension: session concurrency as well as browser time. Do not choose sessions just because the word “browser” sounds closer to your implementation. Choose them when the workflow actually needs an interactive browser.

Dex and Nova compare a single capture from a camera-like rendering unit with a browser window showing several sequential page states.

Conceptual distinction: Quick Actions suit one-shot screenshot/PDF jobs; a scripted browser session is for navigation and interaction across steps.

A useful first cut is:

  • A PDF invoice from known report data: render controlled HTML with a Quick Action.
  • A screenshot of a public URL: use the screenshot Quick Action, with a narrow target allowlist.
  • A workflow that must sign in, navigate, and inspect a result: use a Browser Session and define how cookies, isolation, and cleanup work.

If your current backend is outside Cloudflare, the REST API can be called from that backend. If the request already runs in a Cloudflare Worker, a Workers binding avoids a separate API-token hop. Keep either integration behind your server. The browser credential and account identifiers must never be sent to the client.

Put an application endpoint in front

Avoid making the browser service itself your public product API. Your endpoint should authenticate the customer, check access to the requested record, and decide what content is allowed to be rendered. For PDFs generated from your own data, build the page from a server-owned template and pass the resulting HTML to the renderer. That removes an unnecessary round trip to a public URL and makes the inputs easier to validate.

For URL-based screenshots, treat the destination as untrusted input. Accept only schemes and hostnames your feature needs; reject loopback, private-network, link-local, and metadata-service addresses. Recheck redirects, since an allowed hostname can redirect somewhere else. If customers need to capture arbitrary public URLs, document that boundary and apply the same outbound network protections you would use for any server-side fetch. A browser renderer can visit pages with JavaScript and follow links, so an unchecked URL field can become a server-side request forgery path.

Expose a product-level request such as POST /api/reports/{id}/pdf, not a general endpoint that accepts a browser command. Enforce tenant authorization before rendering, cap page count and output size, set a bounded render timeout, and rate-limit by tenant. Use an idempotency key or a unique render-job ID so a client retry does not silently create repeated work. For a slow or bursty workload, return a job ID and let a worker render it; otherwise a short synchronous response may be simpler. Persist job status in your application so a refresh does not lose the task.

11 production screens. Login and database wired.

The SaaS Dashboard Kit includes login, a database, and a working dashboard. Live demo at saas.otf-kit.dev.

See the live demo

Track cost per completed artifact

Cloudflare documents different billing for Quick Actions and Browser Sessions. Quick Actions are charged by browser hours. Browser Sessions use browser hours and concurrent-browser usage. The published Workers Free limit is ten minutes of browser time per day. The Workers Paid defaults include ten browser hours per month, then charge for additional browser hours; Browser Sessions also have an included concurrency allowance and a separate charge above it. Check the live pricing page before setting your own customer price because plan limits and rates can change.

A request count alone is not a useful cost model. Record each render job, outcome, retry count, duration, tenant, output type, and the browser-time value returned in the X-Browser-Ms-Used header for Quick Actions. Compare that usage with the final bill: Cloudflare totals browser time by day and rounds the monthly total to the nearest whole hour. Your product price should include failed jobs and retries if you absorb their cost, not only successful files.

Start with a small, observable limit. For example, include a modest number of renders per workspace, set a maximum output size, and queue additional jobs. Watch p50 and p95 render duration, timeouts, retries, and actual daily browser hours. If users create many previews at once, test a burst before promising instant completion. A paid plan’s published concurrency is a default account limit, not a guarantee that every render will finish within your product’s latency target.

Treat document data as sensitive

Invoices, account reports, and generated exports can contain personal or commercial data. Render only the fields required for the file, avoid logging full HTML or signed URLs, and store outputs in a private object bucket with short-lived download links. Give each artifact a retention policy and delete it when the user or your product no longer needs it.

Cloudflare says Quick Actions and browser sessions process rendered content ephemerally for most rendering paths, while the asynchronous crawl endpoint and opt-in session recordings have separate retention behavior. Read the current data-handling documentation for the exact path you use. Ephemeral processing at the browser provider does not replace your own access controls, storage policy, or deletion workflow.

Byte and Luna inspect separate locked output cabinets, each holding its own rendered documents and usage beads.

Conceptual application safeguards: isolate tenant outputs and log usage per workspace. The cabinets are not a Cloudflare feature or UI.

For sessions, use a fresh isolated context when one customer’s cookies or cache must not affect another customer’s task. Close the browser in a finally path, and distinguish a renderer failure from a task failure so a retry does not repeat a consequential action. For a screenshot or PDF, prefer Quick Actions and avoid carrying customer login state at all.

Test the product path, not only the API call

Before enabling the feature for every customer, test representative reports: long tables, charts, custom fonts, images, empty states, and mobile-sized content. Check that page breaks are acceptable, text remains selectable where required, and an oversized report is rejected predictably. Exercise a blocked hostname, a redirect, a renderer timeout, an upstream 429, and a customer retry. Verify that authorization runs before any render call and that a retry respects the idempotency key.

Measure one complete flow from the customer’s click to the downloaded artifact. A successful API response is not enough if your job record stays pending, the object is public, or a failed attempt is charged to the wrong tenant. Use real usage headers and the Cloudflare dashboard to compare estimated and billed consumption. Only then decide whether the feature belongs in your base plan, an included monthly quota, or a usage-based add-on.

Keep the first release narrow

A good first release renders one known report type from controlled HTML, caps output size, and returns a private download link. It does not need a general-purpose remote browser product. Add URL screenshots or interactive sessions only when customer demand justifies their additional security and operational surface.

If you still need the application surface around that feature, OTF’s SaaS Dashboard kit is an optional editable Vite, React, Hono, and Postgres UI shell. It is not a Cloudflare Browser Run integration; use it only if that stack is a useful starting point for your own app.

Sources

For the related problem of preserving browser tabs and login state across agent turns, see how to recover a signed-in browser task safely.

architecturebackendtemplates
OTF SaaS Dashboard Kit

Ship the product, not the setup.

  • 11 production screens — auth, team, analytics, and settings
  • Login and a real database are wired in
  • AI configs pre-tuned so your agent extends instead of regenerates
Need more than components?

Full-stack kits.
Pay once, own the code.

SaaS Dashboard and Fitness include authentication and a database. Arcade is browse-first with a local demo session and no backend sign-in by default. Booking is in Preview; Marketplace is coming soon and not currently sold. The Bundle includes the three live kits.

Everything Bundle — $149See full pricing

Get the free AI configs pack

AI configs in every kit: CLAUDE.md, .cursorrules and task prompts. Drop them in and your AI tool instantly understands your project.

No spam. Unsubscribe any time.

Prefer the free SDK? Star it on GitHub →