DEV Community

Cover image for Building a PDF Toolkit That Never Touches a Server
WebsiteGeek
WebsiteGeek

Posted on

Building a PDF Toolkit That Never Touches a Server

Every PDF tool follows the same pattern: upload your file, wait, download the result. That server round-trip exists because PDF processing is genuinely expensive — parsing a file format never designed to be edited, re-encoding pages, running compression. Doing that in a browser tab used to mean either a thin wrapper around a server API, or nothing at all.

That's changed. WebAssembly-compiled versions of real PDF libraries are fast enough now to run entirely client-side, and building DeskRamp meant figuring out what that actually takes inside a Chrome extension, not just a regular web page.

The CSP wall

Manifest V3 extensions get a strict default Content Security Policy, and WebAssembly needs an explicit exception to run at all:

"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
}

Without 'wasm-unsafe-eval' explicitly declared, WebAssembly.instantiate() throws immediately — Chrome treats compiled WASM the same security-sensitive way it treats eval(), since both execute code the browser can't statically analyze ahead of time. This is a one-line fix once you know it's needed, but it's the first thing that breaks silently if you're porting WASM-based processing into an extension for the first time.

Picking a library per task, not one library for everything

No single library covers merge, split, compress, convert, and e-Sign well. DeskRamp bundles a few, each doing the one thing it's actually good at:

pdf.js — rendering and reading PDF structure
pdf-lib — merging, splitting, and building new PDF documents
qpdf (compiled to WASM) — the actual compression and password/decryption work, which needs low-level access to PDF's internal object structure that higher-level libraries don't expose
docx / pptxgen / xlsx libraries — format conversion into and out of Office formats

This means the extension ships several hundred KB to a few MB of vendored library code, which matters for install size and initial load time. The practical fix is lazy-loading: nothing pulls in qpdf's WASM binary until the user actually clicks "Compress," rather than loading every processing library up front for a toolkit most of a session won't touch.

The real ceiling: browser tab memory, not CPU

The unexpected constraint wasn't processing speed — modern WASM PDF processing is fast enough that a merge or split feels instant on anything but a huge file. It's memory. A browser tab has a much lower practical memory ceiling than a server process does, and a large multi-hundred-page PDF fully parsed into memory, plus the WASM runtime's own working memory, plus whatever else the tab is holding, can hit that ceiling in a way a server-side version of the same operation never would.

The mitigation is processing in chunks where the library supports it (streaming page-by-page rather than materializing the whole document object graph at once) and being explicit about releasing references (ArrayBuffers, typed arrays) as soon as a stage finishes, rather than trusting garbage collection to catch up on its own timeline while a multi-step operation is still running.

Why this is worth the extra complexity

The upload step in a typical PDF tool isn't just slower — it's the whole reason people hesitate to use these tools for anything they'd actually rather not hand to a server: contracts, IDs, financial documents. Once WASM-based processing is fast enough to feel native, there's no real reason left to default to a server round-trip for tasks like merge, split, compress, and convert. The extra engineering constraints (CSP, bundle size, memory ceilings) are the actual cost of that tradeoff — worth paying once, not something that needs solving per-feature.

If you're curious what this looks like shipped: DeskRamp is free on the Chrome Web Store.

Top comments (0)