Article

gPdf vs DocRaptor: why edge rendering beats HTML-to-PDF

DocRaptor uses Prince to convert HTML to PDF on a hosted backend. gPdf renders structured JSON directly at the edge. The price gap is 18×. Here's why that's not a teaser.

DocRaptor is a competent product. It wraps Prince — the gold-standard HTML-to-PDF engine — in a hosted REST API, with retries, async jobs, and decent docs. It’s been around for over a decade, and for many teams it’s the obvious “I don’t want to run Prince myself” choice.

We’re a different shape of tool. gPdf doesn’t take HTML at all; it takes structured JSON and renders to PDF directly at the Cloudflare edge. The list-price gap at 100K pages/month is $5/mo (gPdf Basic) vs $89/mo (DocRaptor Basic) — about 18×. That gap isn’t an opening promo. It’s structural. This post explains why the structure produces that price, and where each tool actually fits.

The two architectures, side by side

LayerDocRaptor (HTML → PDF)gPdf (JSON → PDF)
InputHTML + CSS (with Prince extensions for paged media)DocumentRequest JSON
RendererPrince (compiled C++ engine)Custom Rust engine, compiled to WebAssembly
HostingDocRaptor’s centralised servers (US datacentre region)Cloudflare Workers, every CF colo (300+ cities)
Cold startServer-side worker poolV8 isolate boot, single-digit ms
Per-render computeLayout pass over HTML/CSS, then Prince paginatesDirect typesetting, no layout interpretation pass
Per-render p50~250–800 ms wall-clock (network + render)~3–8 ms (network + render)
Output determinismHigh (Prince is mature)Byte-identical (same JSON → same bytes)

If you read those two columns as “general HTML printer” vs “purpose-built document renderer”, you’ve already understood the architectural decision. Everything else (latency, cost, even the feature lists) is downstream of that one choice.

The Prince tax

Prince is good. It’s also doing a job that most invoice/receipt/label workflows don’t need: implementing CSS Paged Media — page-break rules, running headers, footnotes, cross-references, generated content boxes — for arbitrary HTML the user might throw at it.

That generality has a runtime cost. To paginate an arbitrary HTML document, the engine has to:

  1. Parse and validate the HTML
  2. Parse and resolve the CSS cascade (potentially with Prince’s own extensions)
  3. Build a render tree
  4. Run a multi-pass layout (especially for tables that span pages, or columns that balance)
  5. Resolve cross-references across pages
  6. Emit PDF objects

Most of those passes are the cost of accepting HTML as input. If your input is already structured data (which it almost always is — your invoice exists as a JSON object somewhere before you wrap it in HTML), you’re paying for those passes in compute and latency on every render, and they don’t add value to the output.

gPdf skips the layout-interpretation step entirely. The JSON DocumentRequest already specifies the page layout structurally — { pages: [{ size, elements: [...] }] }. The renderer types the elements, paginates tables/lists deterministically, and emits PDF. There’s no CSS cascade to resolve, no float layout to compute, no cross-reference resolution pass.

The result: the same single-page invoice that takes ~300 ms on DocRaptor takes ~3 ms on gPdf. We’re not faster because we wrote a faster Prince — we’re faster because we don’t do most of what Prince does.

”Too cheap to be true” is a real procurement objection

Let’s address this directly because it comes up in every B2B sales call.

“$5/mo for 100K renders. DocRaptor is $89. Anvil is $0.10/PDF (so $10,000 for the same volume). What’s wrong with you?”

Three honest reasons we can charge this:

1. We don’t run a browser

DocRaptor amortises Prince infrastructure across customers. gPdf amortises one Cloudflare Worker, which costs roughly $0.50/million requests on Workers Bundled. With JSON-shaped input, our renderer takes about 1.5 ms of CPU per render. Stack a 50% margin and you’re still in the cents-per-thousand-renders range. The arithmetic is the price.

2. We don’t run a control plane

There are no async jobs, no callbacks, no retry queue, no document storage, no preview-link UI, no multi-tenant database. Every render is a single round-trip to a stateless function and back. That removes the entire ops surface most “PDF API” companies budget for — which is also the surface that justifies their price.

3. The model self-selects out the workloads we’d lose money on

If your document genuinely needs HTML-to-PDF (60-page legal contract, complex CSS-Grid report), you’ll bounce off the JSON model in the first hour and go to DocRaptor anyway. We don’t have to price defensively for those workloads because they self-route. We only have to price for the long-but-narrow tail of “structured-data-to-document” workloads, where the per-render cost is genuinely tiny.

Put together: $5/100K isn’t a loss-leader, it’s the actual cost-of-goods-sold plus margin. We can keep it there indefinitely because the underlying compute is that cheap when you don’t ship a browser.

Where DocRaptor is the right call

We try not to write self-serving comparisons. The cases where DocRaptor genuinely wins:

  • Your input is HTML you don’t fully control. User-generated reports, third-party templates, Markdown-from-CMS-rendered-to-HTML. You don’t want to write a JSON mapper for arbitrary input.
  • You need CSS Paged Media features Prince supports. Running headers/footers per chapter, complex footnote reflow, named-page selectors, generated tables of contents, indexes. gPdf has structured equivalents for the common subset, but if you live in @page :left selectors, Prince is your friend.
  • You have a content team that writes HTML/CSS, not JSON. Don’t impose a JSON authoring workflow on a non-engineering team. They’ll hate you.
  • Async + callbacks + document storage as a service. DocRaptor stores generated PDFs and gives you signed URLs for delivery. gPdf is strictly stateless — your code stores the result.

If you’re in any of those buckets, stay on DocRaptor. It’s the right tool.

Where gPdf is the right call

The mirror image:

  • Your inputs are already structured data (database rows, JSON API payloads, queue messages).
  • Latency matters — interactive checkout flows, real-time label printing, on-demand statement generation.
  • You care about byte-identical reproducibility for tests / audit trails / e-invoice retention.
  • You’re cost-sensitive at any volume above a few thousand renders/month.
  • You need barcodes (GS1-128, QR, Data Matrix, PDF417, Aztec, MaxiCode) at sub-millimetre precision — Prince technically supports SVG barcodes but rendering them at 0.1 mm overall length precision is non-trivial through HTML/CSS.
  • You need PDF/A (1b/2b/3b/4) or Factur-X / ZUGFeRD attachments for compliance.
  • You’d rather not run a JSON-to-HTML-to-PDF pipeline when you can run a JSON-to-PDF pipeline.

Migration is mechanical, not strategic

A common worry: “Switching means rewriting all our templates.” It usually doesn’t. Most HTML-to-PDF templates are 20% layout (which becomes JSON structure once) and 80% data interpolation (which is exactly the same regardless of what the renderer takes).

Practical path:

  1. Pick one document type to migrate. Start with the highest-volume one — biggest savings, smallest blast radius.
  2. Take the HTML template’s data interface (the variables it interpolates) and write a small mapToDocumentRequest(data) function.
  3. Iterate against the Playground until output matches.
  4. A/B in production: route 5% of traffic to gPdf for two weeks. Diff the PDFs. Compare bills.
  5. Roll forward or roll back based on data, not vibes.

We’ve watched teams do this in a single sprint and pocket 90% of their PDF bill in the next month. We’ve also watched teams realise their workload was actually the HTML-to-PDF case and stay on DocRaptor — with our blessing.

TL;DR

DocRaptorgPdf
Best atHTML → PDF for arbitrary contentJSON → PDF for structured documents
Price (100K pages/mo)$89$5
p50 render250–800 ms3–8 ms
Edge-deployed❌ centralised✅ 300+ Cloudflare colos
Async + storage✅ included❌ stateless by design
PDF/A + Factur-X⚠️ via Prince extensions✅ built-in

If your documents are structured data dressed up as HTML for a renderer, you’re paying for a translation step that doesn’t need to exist. Try the Playground — describe one of your invoices in JSON, render it in your browser in under 5 ms, see if the gap matches your gut.