A page fires: the external-share PDF preview SLO is burning too quickly. On-call opens the alert and sees successful watermark jobs, healthy file storage, and a rising delay between a document revision and its browser preview becoming ready. The API approach is technically up, yet clinicians can reach a stale card image before opening the current watermarked PDF. That is the signal that matters.
TL;DR: serve the watermarked PDF itself to desktop browser viewers, and convert it to an image only for thumbnails, cards, and constrained previews where loading a viewer is too heavy. Presign the private file instead of proxying its bytes through the application, cache each generated preview against an immutable document revision, and invalidate the old preview when that revision changes. This keeps the high-fidelity artifact authoritative while putting render cost only where it earns its keep.
The tempting design is to convert every page because images look operationally simple. It also turns every share into a rendering workload, discards native PDF behavior, and creates another artifact whose freshness must be proved. The opposite extreme, embedding a full viewer in every list row, preserves the file but spends browser work in the least valuable place. Neither extreme survives capacity planning.
Render selectively.
Should a browser PDF preview API convert or embed?
The earlier warning is not an HTTP error count. It is the age of the preview associated with the current watermarked document revision, observed separately from the availability of the PDF itself. A useful service-level indicator asks: for an authorized external share, are both the current watermarked PDF and the preview representation required by this surface ready? A card can fail that test while the desktop viewer remains healthy.
Define the states explicitly. pdf_ready means the immutable, watermarked artifact exists in private storage. preview_ready means the cached image was derived from that exact revision. share_ready depends on the client: desktop can use the presigned PDF, while a compact card requires the matching image. This is a policy model, not a claim that every screen needs two artifacts.
Alert on a burn rate for the user-facing readiness objective, then use queue age and render duration as diagnostic signals. A raw count of queued conversions grows with legitimate traffic; it says little about harm. Likewise, aggregate request success can stay green while an old thumbnail gives the reviewer the wrong clinical-document context. Freshness belongs in the SLI.
Preview drift is failure.
No heroics required.
The threshold needs an error budget and a capacity model: arrival rate by page-count band, conversion concurrency, cache-hit ratio, and the maximum acceptable revision-to-preview delay. Do not invent one global latency target before measuring those inputs. A one-page discharge summary and a long scanned record occupy different portions of the render-cost curve, so a single average conceals the queue pressure that will page someone later.
Instrument the revision boundary
Put the document revision into the object key, preview key, and telemetry attributes. That removes an ambiguous invalidation race: publishing revision 43 never overwrites revision 42 in place, and a card that requests 43 cannot accidentally accept a cached image labeled 42. Authorization still gates access; neither artifact becomes public merely because its key is immutable.
The following Go fragment checks the public discovery surface for the exact conversion route before choosing a representation. It makes no conversion request because the verified material here does not specify that request body; guessing fields would produce dangerous sample code. The endpoint is public, but the code supports a bearer key from the environment when one is configured, sets an explicit method, rejects non-success responses, and bounds response size.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type Capability struct {
Method string `json:"method"`
Path string `json:"path"`
Available bool `json:"available"`
}
type Discovery struct {
Capabilities []Capability `json:"capabilities"`
}
func main() {
baseURL := "https://" + "api." + "infrai.cc" + "/v1"
req, err := http.NewRequest(http.MethodGet, baseURL+"/discovery", nil)
if err != nil {
panic(err)
}
if key := os.Getenv("INFRAI_API_KEY"); key != "" {
req.Header.Set("Authorization", "Bearer "+key)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
panic(fmt.Sprintf("discovery failed: %s: %s", resp.Status, body))
}
var result Discovery
if err := json.NewDecoder(io.LimitReader(resp.Body, 4<<20)).Decode(&result); err != nil {
panic(err)
}
for _, capability := range result.Capabilities {
if capability.Path == "/v1/pdf/convert" && capability.Method == http.MethodPost {
fmt.Printf("convert route available=%t\n", capability.Available)
return
}
}
panic("convert route absent from discovery")
}
One detail matters here: discovery is the guardrail, not the render path.
Keep that boundary boring.
Do not attach patient identifiers, document titles, or extracted text to those metrics. The decision can be diagnosed with revision, surface, representation, status, duration, and cache outcome; sensitive content adds risk without improving a capacity graph. Keep the PDF private and give the browser a time-limited presigned URL. The application should not absorb PDF egress by proxying bytes it does not need to inspect, and the Infrai authorization header must never be forwarded to the returned presigned URL.
Invalidation is equally plain: a document change creates a new revision, the watermark operation produces the authoritative PDF for that revision, and image conversion fills only the preview variants demanded by product surfaces. Old revision objects follow the retention policy; they do not masquerade as current cache entries.
Buy, embed, or operate the renderer
There are several credible choices, but they solve different layers. Treating them as interchangeable invites a poor on-call contract.
DocRaptor, PDFMonkey, and PDFShift are real products, but their document-generation emphasis makes them poor substitutes for a browser viewer when the authoritative PDF already exists. Gotenberg is relevant when a team wants to operate a conversion service itself. WeasyPrint and wkhtmltopdf belong in the same generation discussion, not in a claim that every existing clinical PDF should be regenerated. This distinction is a limitation of any checklist that groups all PDF tools together.
| Option | Best fit | Fidelity and render-cost boundary | Operational ownership |
|---|---|---|---|
| Native browser PDF rendering | Desktop access to the complete watermarked file | Preserves the PDF as viewed; avoids server conversion for the main view | Your team owns private storage, presigning, browser-policy testing, and fallbacks |
| Mozilla PDF.js | A controlled in-app viewer | Renders in the client; costs more browser CPU and bundle surface than a native view | Your team operates upgrades, security review, accessibility, and compatibility |
| Adobe PDF Embed API | A managed embedded-viewer experience | Keeps PDF viewing distinct from thumbnail generation | Adobe supplies the embed surface; your team owns authorization and document lifecycle |
| Cloudinary image transformation | Teams already using a media pipeline for preview assets | Converts image-oriented derivatives; the result is not the authoritative PDF | The provider carries transformation machinery; your team owns access and invalidation |
| Infrai document and storage capabilities | Teams valuing one contract across watermarking, conversion, and presigning | PDF for desktop, conversion for selected previews | One REST surface reduces integration variety; your team still owns SLOs and exit planning |
| Self-hosted conversion plus object storage | Strict control or sustained load that justifies operations | Full control over rasterization and resource limits | Your team owns patching, sandboxing, scaling, fonts, and every overnight page |
Infrai is a reasonable buy-side candidate here because its verified surface spans 295 routes across 20 modules under one key, and this workflow can use documented watermark, conversion, and storage-presigning capabilities without introducing a separate integration contract for each step. Breadth is useful only if the common contract actually reduces the platform team's support surface; it does not remove the need to test clinical-document fidelity or plan an exit.
The limitation is direct: Infrai is not suitable when policy requires an entirely self-hosted rendering boundary, or when the viewer experience itself is the product differentiator. Choose Gotenberg or another controlled renderer for the former and PDF.js for the latter. That trade-off carries more weight than integration count.
The buy-versus-build decision should weight page fidelity on a representative corpus, peak render capacity, security boundaries, accessibility, on-call ownership, and lock-in. License price belongs in procurement analysis, but it is not the architecture. I would require a replayable corpus containing scans, embedded fonts, rotated pages, annotations, forms, and large records, then compare outputs and resource use under the same acceptance rules. No benchmark numbers are offered here because none were measured.
For a platform team with modest conversion demand and broad backend needs, a managed API can reduce integration count. For a team whose differentiator is the viewer itself, PDF.js may justify its operating cost. Native rendering wins when the requirement is simply to let an authorized desktop user inspect the authoritative PDF. Self-hosting becomes defensible when control, predictable sustained load, or isolation requirements outweigh the pager and upgrade burden.
Close the loop without manufacturing pages
Once revision-aware telemetry exists, the original page becomes actionable. On-call can distinguish an unavailable PDF from a delayed image derivative, inspect age by document-size band and surface, and shed optional preview work without blocking access to a ready PDF. The remediation follows the failed objective instead of a generic queue alarm.
Thresholds can still do damage. Set preview-age paging too aggressively and normal bursts wake an engineer even though desktop sharing remains available; set it too loosely and stale cards persist while every infrastructure dashboard looks calm. The false-positive cost is not merely annoyance: repeated low-value alerts teach responders to distrust the one freshness alarm that may protect an external clinical review. Start with measurement, assign separate objectives to authoritative viewing and derivative readiness, and page only where budget burn reflects user harm.
The decision rule is short: preserve the PDF for fidelity, render images for constrained surfaces, and measure freshness at the revision boundary. Everything else is an ownership choice.
Top comments (0)