DocRaptor là một sản phẩm tốt. Nó bọc Prince, engine HTML-to-PDF chuẩn mực, thành một hosted REST API có retry, async jobs và tài liệu khá ổn. Sản phẩm đã tồn tại hơn một thập kỷ, và với nhiều đội, đó là lựa chọn tự nhiên khi họ nghĩ: “tôi không muốn tự vận hành Prince”.
gPdf là một kiểu công cụ khác. gPdf không nhận HTML; nó nhận JSON có cấu trúc và render trực tiếp thành PDF tại Cloudflare Edge. Ở 100.000 trang/tháng, giá niêm yết là 5 USD/tháng với gPdf Basic so với 89 USD/tháng với DocRaptor Basic, tức khoảng 18 lần. Khoảng cách đó không phải khuyến mãi mở đầu. Nó đến từ kiến trúc. Bài này giải thích vì sao kiến trúc tạo ra mức giá đó, và mỗi công cụ thật sự phù hợp ở đâu.
Hai kiến trúc đặt cạnh nhau
| Lớp | DocRaptor (HTML -> PDF) | gPdf (JSON -> PDF) |
|---|---|---|
| Input | HTML + CSS, kèm extension Prince cho paged media | JSON DocumentRequest |
| Renderer | Prince, engine C++ đã compile | Engine Rust tùy chỉnh, compile sang WebAssembly |
| Hosting | Server tập trung của DocRaptor, vùng datacenter ở Mỹ | Cloudflare Workers, mọi CF colo, hơn 300 thành phố |
| Cold start | Worker pool phía server | V8 isolate boot, single-digit ms |
| Compute mỗi lần render | Layout pass trên HTML/CSS, sau đó Prince phân trang | Typesetting trực tiếp, không có bước diễn giải layout |
| p50 mỗi lần render | khoảng 250-800 ms wall-clock, gồm network và render | khoảng 3-8 ms, gồm network và render |
| Determinism của output | Cao, vì Prince rất mature | Byte-identical, cùng JSON tạo cùng bytes |
Nếu bạn đọc hai cột này thành “máy in HTML tổng quát” so với “document renderer chuyên dụng”, bạn đã nắm được quyết định kiến trúc chính. Tất cả phần còn lại, từ latency, chi phí đến danh sách tính năng, đều đi sau lựa chọn đó.
Thuế Prince
Prince rất tốt. Nhưng nó cũng làm một việc mà phần lớn workflow hóa đơn, biên nhận và nhãn không thật sự cần: triển khai CSS Paged Media cho HTML tùy ý mà người dùng đưa vào, gồm quy tắc ngắt trang, running header, footnote, cross-reference và generated content box.
Tính tổng quát đó có chi phí lúc chạy. Để phân trang một tài liệu HTML tùy ý, engine phải:
- Parse và validate HTML
- Parse và resolve CSS cascade, có thể kèm extension riêng của Prince
- Xây render tree
- Chạy layout nhiều pass, đặc biệt với bảng trải qua nhiều trang hoặc cột cần cân bằng
- Resolve cross-reference giữa các trang
- Emit PDF objects
Phần lớn các pass này là chi phí của việc chấp nhận HTML làm input. Nếu input của bạn vốn đã là dữ liệu có cấu trúc, mà gần như luôn là vậy vì invoice thường tồn tại dưới dạng JSON trước khi được bọc thành HTML, bạn đang trả compute và latency cho các pass đó ở mỗi lần render mà không nhận thêm giá trị tương ứng.
gPdf bỏ qua hoàn toàn bước diễn giải layout. JSON DocumentRequest đã mô tả layout trang bằng cấu trúc: { pages: [{ size, elements: [...] }] }. Renderer typeset element, phân trang bảng/list theo cách deterministic và emit PDF. Không có CSS cascade để resolve, không có float layout để tính, không có pass resolve cross-reference.
Kết quả: cùng một invoice một trang mất khoảng 300 ms trên DocRaptor thì mất khoảng 3 ms trên gPdf. Chúng tôi không nhanh hơn vì viết một Prince nhanh hơn; chúng tôi nhanh hơn vì không làm phần lớn công việc mà Prince phải làm.
“Rẻ quá có đáng tin không” là một phản đối procurement thật
Nói thẳng về điểm này, vì nó xuất hiện trong gần như mọi cuộc gọi B2B sales.
“5 USD/tháng cho 100.000 render. DocRaptor là 89 USD. Anvil là 0,10 USD/PDF, tức 10.000 USD cho cùng volume. Có vấn đề gì với các bạn vậy?”
Ba lý do thẳng thắn khiến chúng tôi có thể đặt giá như vậy:
1. Chúng tôi không chạy browser
DocRaptor phân bổ hạ tầng Prince trên nhiều khách hàng. gPdf phân bổ một Cloudflare Worker, với chi phí Workers Bundled xấp xỉ 0,50 USD cho 1 triệu request. Với input dạng JSON, renderer của chúng tôi dùng khoảng 1,5 ms CPU cho mỗi render. Cộng thêm 50% margin vẫn chỉ ở mức vài cent trên mỗi nghìn render. Phép tính chính là mức giá.
2. Chúng tôi không vận hành control plane
Không async jobs, không callback, không retry queue, không lưu tài liệu, không preview-link UI, không database multi-tenant. Mỗi lần render là một round trip duy nhất tới một hàm stateless rồi quay lại. Điều đó loại bỏ toàn bộ bề mặt vận hành mà nhiều công ty “PDF API” phải tính vào ngân sách, cũng là bề mặt thường được dùng để biện minh cho giá cao hơn.
3. Mô hình tự loại các workload khiến chúng tôi lỗ
Nếu tài liệu của bạn thật sự cần HTML-to-PDF, ví dụ hợp đồng pháp lý 60 trang hoặc báo cáo CSS Grid phức tạp, bạn sẽ không hợp với mô hình JSON ngay trong giờ đầu và sẽ quay lại DocRaptor. Chúng tôi không cần định giá phòng thủ cho các workload đó, vì chúng tự phân luồng. Chúng tôi chỉ cần định giá cho dải “dữ liệu có cấu trúc -> tài liệu” dài nhưng hẹp, nơi chi phí mỗi lần render thật sự rất nhỏ.
Kết hợp lại, 5 USD/100.000 không phải loss leader; đó là cost-of-goods-sold thật cộng margin. Chúng tôi có thể giữ mức đó lâu dài vì compute nền thật sự rẻ như vậy khi bạn không đưa browser vào pipeline.
Khi nào DocRaptor là lựa chọn đúng
Chúng tôi không muốn viết so sánh chỉ có lợi cho mình. Đây là những trường hợp DocRaptor thật sự thắng:
- Input của bạn là HTML bạn không kiểm soát hoàn toàn. Báo cáo do người dùng tạo, template bên thứ ba, Markdown từ CMS được render thành HTML. Bạn không muốn viết JSON mapper cho input tùy ý.
- Bạn cần tính năng CSS Paged Media mà Prince hỗ trợ. Running header/footer theo chương, footnote reflow phức tạp, named-page selectors, generated table of contents, index. gPdf có các tương đương có cấu trúc cho phần phổ biến, nhưng nếu bạn sống trong selector
@page :left, Prince là bạn của bạn. - Đội content viết HTML/CSS, không viết JSON. Đừng ép một đội không phải engineering dùng workflow authoring bằng JSON. Họ sẽ ghét bạn.
- Async, callback và document storage như một service. DocRaptor lưu PDF đã tạo và đưa signed URL để giao hàng. gPdf thiết kế strictly stateless: code của bạn tự lưu kết quả.
Nếu bạn thuộc các nhóm đó, hãy ở lại DocRaptor. Đó là công cụ đúng.
Khi nào gPdf là lựa chọn đúng
Chiều ngược lại:
- Input của bạn đã là dữ liệu có cấu trúc: database rows, JSON API payloads, queue messages.
- Latency quan trọng: checkout tương tác, in nhãn realtime, tạo sao kê theo yêu cầu.
- Bạn cần byte-identical reproducibility cho test, audit trail hoặc e-invoice retention.
- Bạn nhạy với chi phí ở bất kỳ volume nào trên vài nghìn render/tháng.
- Bạn cần barcode, như GS1-128, QR, Data Matrix, PDF417, Aztec, MaxiCode, với độ chính xác dưới millimetre. Prince có thể render SVG barcode, nhưng giữ tổng chiều dài chính xác 0,1 mm qua HTML/CSS không hề đơn giản.
- Bạn cần PDF/A (1b/2b/3b/4) hoặc attachment Factur-X / ZUGFeRD cho compliance.
- Bạn không muốn chạy pipeline JSON-to-HTML-to-PDF khi có thể chạy thẳng JSON-to-PDF.
Migration mang tính cơ học hơn là chiến lược
Một lo lắng phổ biến: “Chuyển đổi nghĩa là viết lại toàn bộ template.” Thường thì không. Phần lớn template HTML-to-PDF gồm 20% layout, phần này trở thành cấu trúc JSON một lần, và 80% data interpolation, phần này giống nhau bất kể renderer nhận gì.
Lộ trình thực tế:
- Chọn một loại tài liệu để migrate. Bắt đầu với loại volume cao nhất: tiết kiệm lớn nhất, blast radius nhỏ nhất.
- Lấy data interface của HTML template, tức các biến nó interpolate, rồi viết một hàm nhỏ
mapToDocumentRequest(data). - Lặp trong Playground cho đến khi output khớp.
- A/B trong production: route 5% traffic sang gPdf trong hai tuần. Diff PDF. So sánh hóa đơn.
- Roll forward hoặc rollback dựa trên dữ liệu, không dựa trên cảm giác.
Chúng tôi đã thấy đội làm việc này trong một sprint và giảm 90% hóa đơn PDF ngay tháng sau. Chúng tôi cũng đã thấy đội nhận ra workload của họ thật sự là HTML-to-PDF và ở lại DocRaptor với lời chúc tốt từ chúng tôi.
TL;DR
| DocRaptor | gPdf | |
|---|---|---|
| Mạnh nhất ở | HTML -> PDF cho nội dung tùy ý | JSON -> PDF cho tài liệu có cấu trúc |
| Giá 100.000 trang/tháng | 89 USD | 5 USD |
| p50 render | 250-800 ms | 3-8 ms |
| Deploy tại Edge | Không, tập trung | Có, hơn 300 Cloudflare colo |
| Async + storage | Có sẵn | Không, stateless theo thiết kế |
| PDF/A + Factur-X | Qua extension của Prince | Built-in |
Nếu tài liệu của bạn là dữ liệu có cấu trúc nhưng được mặc áo HTML để đưa cho renderer, bạn đang trả tiền cho một bước dịch không cần tồn tại. Hãy thử Playground: mô tả một invoice bằng JSON, render trong browser dưới 5 ms, rồi xem khoảng cách có đúng như trực giác của bạn không.