DEV Community

Cover image for Building Fillora PDF: Why We Moved Heavy PDF Processing to Client-Side WebAssembly
Rick M
Rick M

Posted on

Building Fillora PDF: Why We Moved Heavy PDF Processing to Client-Side WebAssembly

Building Fillora PDF: Moving PDF Processing from Server APIs to Browser-Native Execution

Every developer who has built document conversion features has probably encountered the same challenges: large file uploads, server memory pressure, processing queues, timeout limits, and users who hesitate to upload sensitive documents to third-party servers.

When building Fillora PDF, we redesigned many of our core PDF manipulation tools to run directly inside the browser instead of relying on server-side processing.

This article explains why we made that architectural decision, how modern browser APIs make it practical, and the trade-offs we discovered along the way.


🏗️ Server Processing vs. Browser-Native Processing

Traditional online PDF tools typically process every operation on a remote server.

Traditional Server Workflow

Browser
   │
   ├── Upload PDF
   ▼
Server
   │
   ├── Process Document
   ▼
Browser
   └── Download Result
Enter fullscreen mode Exit fullscreen mode

While this approach works well, it introduces several challenges:

  • Upload latency for large files
  • Higher server CPU and memory usage
  • Increased bandwidth costs
  • Privacy concerns when processing sensitive documents
  • Scalability challenges during traffic spikes

For many PDF editing operations—such as merging, splitting, rotating, rearranging pages, and metadata editing—the browser is now capable of doing the work locally.

Browser-Native Workflow

Browser
   │
   ├── Read Local File
   ├── Process Document
   └── Download Result
Enter fullscreen mode Exit fullscreen mode

In this model:

  • Documents do not need to be uploaded for supported browser-native operations.
  • Processing happens on the user's device rather than consuming server CPU.
  • Users receive immediate results without waiting for upload and download cycles.

⚡ Why Modern Browsers Can Handle PDF Processing

Modern browsers provide powerful APIs that make client-side document processing practical.

Some of the key building blocks include:

  • FileReader API for reading local files
  • Blob and URL.createObjectURL() for generating downloadable files
  • ArrayBuffer and Uint8Array for binary data manipulation
  • JavaScript libraries such as pdf-lib for PDF editing
  • Web Workers for moving heavy processing off the main UI thread

A simplified merge example looks like this:

import { PDFDocument } from "pdf-lib";

async function mergePdfFiles(buffers) {
  const mergedPdf = await PDFDocument.create();

  for (const buffer of buffers) {
    const source = await PDFDocument.load(buffer);

    const pages = await mergedPdf.copyPages(
      source,
      source.getPageIndices()
    );

    pages.forEach((page) => mergedPdf.addPage(page));
  }

  return await mergedPdf.save();
}
Enter fullscreen mode Exit fullscreen mode

Because the entire operation runs locally, many everyday PDF tasks can complete quickly without requiring the document to leave the user's device.


🧵 Keeping the Interface Responsive

One challenge with client-side processing is preventing expensive operations from freezing the interface.

Heavy computations performed on the main thread can block rendering and make the application feel unresponsive.

To avoid this, long-running tasks can be executed inside Web Workers, allowing the browser to continue rendering the interface while processing documents in the background.

The result is a smoother experience, especially when working with larger files.


⚖️ Trade-offs We Encountered

Moving processing into the browser is not a universal solution.

Several practical limitations remain.

1. Browser Memory Limits

Browsers enforce memory limits per tab.

Very large PDFs or documents containing thousands of pages can exhaust available memory, particularly on mobile devices.

2. Complex Document Formats

Converting richly formatted Office documents into PDF often requires sophisticated rendering engines.

These workloads are generally better suited to dedicated server-side services.

3. Bundle Size

Adding every processing engine to the initial application bundle would significantly increase page load times.

We therefore load only the functionality required for the current tool whenever possible.

4. Fallback Processing

Some workloads simply exceed what browsers can comfortably handle.

In those situations, server-side processing remains an important fallback.


📈 What Changed

For browser-native PDF operations we observed several practical benefits:

  • Reduced server infrastructure costs
  • Lower bandwidth consumption
  • Faster perceived performance by eliminating upload delays
  • Improved privacy for supported workflows
  • Better scalability because document processing no longer depends on server compute

Rather than replacing server processing entirely, we found that a hybrid architecture works best:

  • Browser-native execution for supported PDF editing tasks
  • Server-side processing only when the workload genuinely requires it

💬 Final Thoughts

Modern browsers have evolved into powerful application platforms.

Features that once required dedicated backend infrastructure can now often be performed directly on the user's device using standard browser APIs and mature JavaScript libraries.

For us, moving many PDF operations into the browser simplified our infrastructure while providing a faster and more privacy-friendly experience for users.

If you've built browser-native document tools, I'd love to hear what approaches worked well for you.

You can explore our implementation at https://fillorapdf.com.

Top comments (2)

Collapse
 
to21as profile image
Tobias

The hybrid conclusion is the right one, and two of your trade-offs interact in a way worth sharpening.

On memory: with pdf-lib the peak isn't the largest input, it's every parsed source still in scope plus the serialized output, since save() builds one contiguous Uint8Array at the end. Dropping each source buffer right after copyPages helps, but the floor stays the merged doc plus its serialization. The part that bites is how it fails. On iOS Safari you don't get a catchable OOM, the tab dies, so your point 4 (server-side fallback) can't be reached from the failure it exists to cover. Size-gating before you start beats a try/catch here.

On metadata editing specifically: if the input is a PDF/A file, editing metadata in the browser is an easy way to silently break its conformance. The XMP packet has to stay in sync with the DocInfo dict, and the PDF/A identification schema (pdfaid:part, pdfaid:conformance) has to survive the rewrite. pdf-lib will happily produce a file that opens fine in every viewer and no longer validates. If anyone downstream of your metadata tool has an archival requirement, that's the one operation I'd route to the server, less because the browser can't do the edit and more because the check that proves it's still conformant is the thing you don't want the client to be the judge of.

Congrats, your website looks great!

Collapse
 
rickkm profile image
Rick M

Thanks! I really appreciate the detailed feedback.

You're absolutely right about the memory behavior. Fillora follows a hybrid architecture, while the Fillora Browser SDK focuses on browser-native processing for supported workloads. Your point about size-gating on memory-constrained browsers like iOS Safari is especially valuable, and it's something we'll continue to consider as we evolve the platform.

The PDF/A point is also a great callout. Our browser-native metadata tools are designed for general PDF workflows, while compliance-sensitive scenarios like archival validation are better suited to dedicated server-side processing and validation.

Thanks again for taking the time to share these insights—and for the kind words about the website!