Home  ›  Guides  ›  Ecosystem use cases

Ecosystem use cases

Four production architectures, each built from parts the ecosystem already ships. Store a kilobyte of JSON instead of a megabyte of PDF and render on the reader's own device; sign to PAdES B-LTA across an air gap; gate template changes in CI with a real document diff; and serve edge-rendered PDFs with honest HTTP caching. Every arrow in the diagrams below is a public, shipped API — nothing here is aspirational.

The capability × surface matrix tells you what each surface can do; this guide shows how the surfaces compose into systems. Each case names its exact building blocks, shows the load-bearing code, and states its limits.

Case 1 — Store the spec, not the PDF#

A typical generated PDF weighs hundreds of kilobytes — most of it embedded fonts and compressed streams that are recomputed identically on every render. The document's actual information content is the text and structure, which fits in a few kilobytes of JSON. So: persist the compact DocSpec (or a DocumentParams JSON), and let the device that asks for the PDF produce it.

Architecture: a server stores kilobyte-sized DocSpec JSON documents in a database instead of megabyte-sized PDFs in object storage. When a client requests a document, the server sends the JSON; the client's own browser renders it to a PDF locally with pdfnative-react and the zero-dependency pdfnative engine — uncompressed by default, or compressed by plugging a synchronous deflate implementation into setDeflateImpl. Storage shrinks by roughly two orders of magnitude and the PDF bytes never transit the network.

Why this works in a browser at all: the engine has zero dependencies and no Node-only code paths on this route. Rendering is synchronous and the output is a valid, uncompressed PDF — and since the bytes stay on the device, their size costs nothing on the wire.

// On the reader's device — nothing but the two packages and React.
import { renderSpecToBytes, downloadBlob } from 'pdfnative-react';

const spec = await fetch(`/api/documents/${id}`).then((r) => r.json()); // ~2 KB
const bytes = renderSpecToBytes(spec);       // rendered locally, synchronously
downloadBlob(bytes, 'invoice.pdf');          // builds the Blob itself

Want compressed output too? setDeflateImpl accepts a synchronous deflate function whose result is written into the file verbatim, so it must produce a zlib-wrapped (RFC 1950) stream — /FlateDecode is zlib per ISO 32000-1 §7.3.8. The browser's CompressionStream cannot be plugged in (it is asynchronous by nature); a tiny synchronous library can:

import { zlibSync } from 'fflate';           // synchronous, zlib-wrapped output
import { setDeflateImpl, renderSpecToBytes } from 'pdfnative-react';

setDeflateImpl((bytes) => zlibSync(bytes));
const compressed = renderSpecToBytes({ ...spec, layout: { ...spec.layout, compress: true } });

What you gain, concretely:

Honest limits: the client re-renders with the engine version it has, so a spec is not a byte-frozen archival artefact — for documents that must never change (or that carry signatures), render once and archive the bytes (see Case 2). Non-Latin scripts need their font modules delivered to the client (each is a lazily imported module; ship only what the spec's languages need). The engine renders identical bytes for identical input on the same version — pin the engine when reproducibility matters.

Case 2 — Air-gapped PAdES B-LTA archiving#

Long-term signature validation has a paradox: the evidence that keeps a signature verifiable for decades (OCSP responses, CRLs, timestamps) comes from the network — but the most sensitive signing environments are exactly the ones with no network. CLI 1.4.0 resolves it by splitting the ladder: evidence is collected as replayable JSON on a connected machine, and embedded fully offline inside the enclave.

Architecture: two network zones. In the connected zone, a machine runs pdfnative sign with the PAdES profile and an RFC 3161 timestamp URL, reaching PAdES B-T, then ltv collect with the online opt-in gathers OCSP responses and CRLs into a replayable ltv-data.json file. Only that JSON file crosses the controlled transfer boundary into the air-gapped enclave, where ltv embed writes the evidence into the PDF's DSS dictionary with no network access, reaching B-LT, and doc-timestamp appends a document-timestamp revision for B-LTA on the way out through the gateway.

# Connected zone — sign with a timestamp (B-T), then gather the evidence.
pdfnative sign --input contract.pdf --output signed.pdf \
  --profile pades \                                    # ETSI.CAdES.detached
  --timestamp https://tsa.example.com/rfc3161          # RFC 3161, SSRF-guarded
pdfnative ltv collect --input signed.pdf --online --output ltv-data.json

# ── controlled transfer of ltv-data.json across the air gap ──

# Enclave — no network I/O is even possible in `embed`, by design.
pdfnative ltv embed --input signed.pdf --data ltv-data.json --output signed.lt.pdf

# Gateway (network) — cap the ladder with a document timestamp (B-LTA).
pdfnative doc-timestamp --input signed.lt.pdf \
  --url https://tsa.example.com/rfc3161 --output signed.lta.pdf

# Anywhere, later — verify the whole ladder offline from the embedded /DSS.
pdfnative verify --input signed.lta.pdf --strict --revocation offline

--profile pades is not optional here: the default is pkcs7, which writes /SubFilter /adbe.pkcs7.detached — a valid CMS signature, but not an ETSI EN 319 142-1 baseline, so the whole B-T → B-LTA ladder would sit on a non-PAdES foundation.

The evidence file is versioned JSON (pdfnative schema ltv-data) with DER payloads as base64 — auditable before it crosses the boundary, replayable if the embed step must be re-run. Every network touch on the connected side is an explicit per-invocation opt-in behind an SSRF guard (http/https only, private addresses blocked, no redirects). Years later, run doc-timestamp again to renew the protection before the TSA certificate expires — earlier revisions stay byte-identical.

Honest limits: ltv embed trusts the evidence it is given — the transfer process, not the tool, is what makes the air gap meaningful. The MCP surface offers the same ladder (add_ltv, timestamp_pdf) with operator-configured transport instead; the React renderer deliberately does not sign (post-process its output with the CLI or the library).

Case 3 — A document-regression gate in CI#

Templates drift. A dependency bump changes a wrap point, a copy edit pushes a table onto page 3, a refactor silently drops the footer — and nobody notices until a customer does, because PDFs in CI are write-only artefacts nobody opens. CLI 1.4.0's compare turns them into asserted artefacts: exit 0 when the golden and the candidate agree, exit 1 with a diff report when they do not.

Architecture: a CI pipeline triggered by a pull request renders the changed template to a candidate PDF with render using the strict PDF/A gate, then runs pdfnative compare between the committed golden PDF and the candidate. Identical documents exit 0 and the check passes; differences print a text-and-structure report and exit 1, failing the pull request. An optional veraPDF step validates the PDF/A claim, and an approved change promotes the candidate to become the new golden file.

# .github/workflows/documents.yml
- name: Render candidate (hard-fail on PDF/A diagnostics)
  run: pdfnative render --input templates/invoice.json \
         --output out/candidate.pdf --tagged pdfa2b --strict

- name: Diff against the golden file
  run: pdfnative compare golden/invoice.pdf out/candidate.pdf \
         --mode both --ignore-whitespace --format json
  # identical → exit 0 · different → report on stdout, then exit 1 / E_CHECK_FAILED

The both mode diffs the extracted reading-order text and the declared structure — page count, page and print boxes, metadata, form fields, annotations, encryption, signatures — with a --tolerance in points for geometry. render --strict upgrades PDF/A diagnostics (an unembedded form font, a DeviceCMYK image under the wrong profile) from warnings to a failed build before the first output byte. When a change is intentional, promoting the candidate to golden is a one-line copy in the same PR.

Honest limits: compare is not a visual diff — pdfnative has no rasteriser, so pixel-level rendering changes that alter neither text nor structure pass; pair it with an external rasteriser if that matters to you. Note that engine upgrades can legitimately change bytes without changing content (the diff compares content, not bytes — which is exactly why it works across engine versions where a checksum would not).

Case 4 — Edge-rendered PDFs with honest HTTP caching#

Rendering PDFs in a central service means queues, SaaS round-trips, or a container with a headless browser. The engine's zero-dependency, Fetch-native design removes the constraint: renderToResponse returns a web-standard Response, so the same code runs in a Next.js route handler, Cloudflare Workers, Deno Deploy or Bun — and since pdfnative-react 1.2.0 it speaks real HTTP caching.

Architecture: browsers request a PDF from an edge or serverless runtime. The handler calls renderToResponse from pdfnative-react, which streams the PDF with constant memory and sets Cache-Control and a strong ETag derived from the rendered bytes. The CDN in front caches the response; repeat requests are served from cache, and revalidation requests answered with 304 Not Modified cost no render at all. No central PDF service, no headless browser, no SaaS round-trip.

// app/invoice/[id]/route.tsx — identical on Workers / Deno / Bun.
import { renderToResponse } from 'pdfnative-react';

export async function GET(_req: Request, { params }: { params: { id: string } }) {
  const data = await loadInvoice(params.id);
  return renderToResponse(<Invoice data={data} />, {
    fileName: `invoice-${params.id}.pdf`,
    cacheControl: 'public, max-age=300, s-maxage=86400',
    etag: true, // strong validator derived from the rendered bytes
  });
}

cacheControl is emitted verbatim, so the CDN tier does the heavy lifting; etag: true derives a strong validator from the actual bytes, so conditional requests revalidate correctly and a 304 Not Modified costs no render. Streamability is validated before the first byte — a document that cannot stream (a table of contents, {pages} placeholders) fails up-front instead of mid-response with the headers already sent.

Honest limits: etag: true must buffer the response to hash it — for very large documents choose either streaming (constant memory, no strong ETag) or the buffered validator, not both. A per-user document should say private in its Cache-Control, and remember the PDF inherits whatever authorisation bug the route has — cache keys must include whatever distinguishes users.

Picking parts, not a platform#

The four cases share one property: each is assembled from surfaces that also work alone, so none of them locks you in. The specs of Case 1 render fine server-side the day you need a byte archive; the golden files of Case 3 are ordinary PDFs any tool can open; the ladder of Case 2 verifies in Adobe Acrobat, not just in pdfnative verify. Start with the case closest to your bottleneck and borrow pieces from the others as they become relevant.

See also#