Every time you want to compress an NDA, sign a lease agreement, or compare contract drafts online, mainstream tools (like Smallpdf or ILovePDF) ask you to do the exact same thing: upload your sensitive document to their remote server.
Even when services use encryption in transit and promise to delete files after a limited period, uploading confidential documents to a remote server still means entrusting those documents to a third-party service. Encryption in transit protects the transfer; it doesn't eliminate the need to trust the system that receives and processes the document. Furthermore, depending on the type of files and your use case (e.g., legal or medical), uploading them to a third-party service may not be an option due to privacy, confidentiality, or regulatory requirements.
To solve this, I built beePDF, a 100% client-side Progressive Web App (PWA) where document processing happens entirely in the browser and documents are never uploaded to a remote server.
Here is a breakdown of how it works, why I chose OPFS coupled with IndexedDB, and the architectural tradeoffs of building local first document tools on the modern web platform.
š ļø The Tech Stack
- Framework: Nuxt (Vue 4, TypeScript, Vite)
-
PWA & Offline Support:
@vite-pwa/nuxt+ Service Workers -
In-Memory File Processing:
pdf-lib,PDF.js, native Canvas API -
Local Persistence: Origin Private File System (
navigator.storage.getDirectory()) - Styling & UI: Pico CSS. Yes, I'm a dinosaur that prefers to write minimal CSS rather than reaching for tailwind.
Having previously worked with Next.js, I have to say that Nuxt is a breath of fresh air. If you haven't already taken it for a spin, I cannot recommend it highly enough.
š” Why OPFS (Origin Private File System) Changed Everything
When building a local-first web app that handles binary documents, local persistence becomes a major bottleneck:
- LocalStorage: Limited to ~5MB-10MB and synchronous text-only storage completely useless for binary PDFs.
- IndexedDB: Excellent for structured metadata and smaller binary objects, but less natural for treating large documents as persistent files with efficient byte-level access.
- File System Access API: Great when an app needs to work directly with files in the user's filesystem, but access requires user permission and is subject to browser security restrictions.
Enter OPFS
The Origin Private File System (OPFS) provides a private, origin-scoped filesystem designed for persistent file-like data, with low-level byte access and optimized in-place writes. It also offers synchronous access handles inside Web Workers for performance-sensitive workloads.
// Accessing the local OPFS root directory
const root = await navigator.storage.getDirectory();
const draftHandle = await root.getFileHandle("working-draft.pdf", { create: true });
// Writing binary data stream directly to local disk
const writable = await draftHandle.createWritable();
await writable.write(pdfArrayBuffer);
await writable.close();
Why OPFS is crucial for PDF processing:
Zero Permission Prompts: Because the directory is private to https://beepdf.app, the browser doesn't spam the user with permission modals.
Blazing Fast Disk I/O: OPFS provides direct byte-level file streams. In Web Workers, you can even use createSyncAccessHandle() for near-native, synchronous file read/write operations.
Storage Quotas & Cleanup: Files persist across browser reloads (so you don't lose your working document if you accidentally close the tab), but automatically honor the browser's origin storage quota.
āļø How the Processing Pipeline Works
When you drag and drop a PDF into beePDF:
Local Intake: The browser reads the File object into an ArrayBuffer in memory.
OPFS: The document is immediately stored in the local OPFS workspace directory.
Meta data and a very small thumbnail are stored in indexDB. This also makes searching and tagging trivial.
Web Worker Offloading: Tasks like rendering page thumbnails, extracting plain text / Markdown, or running word-level diffs are offloaded to Web Workers to keep the UI running at 60 FPS.
Client-Side Export: Modifying or signing a document creates a fresh Uint8Array in memory, which triggers a direct client download via a URL.createObjectURL Blob.
Airplane Mode Test: You can load beePDF, turn off your Wi-Fi or disconnect your internet completely, and merge, split, compress, or sign documents offline.
š¤ Challenges & Lessons Learned
Modern browsers provide a remarkably rich set of capabilities, many of which can be accessed by web applications through standardized Web APIs. Building pure client-side file tools, however, isn't without its limits:
Browser Memory Ceilings: Processing massive 500MB+ scanned PDFs inside browser JS can hit V8 heap memory limits on lower-end devices.
Main Thread Blocking: Heavy image compression or canvas manipulation must be offloaded to Web Workers; otherwise, rendering high-DPI canvases will freeze the UI thread.
š¬ Let's Discuss!
Iād love to get feedback from the DEV community!
Have you used OPFS in production yet? How does it compare to IndexedDB for your use cases?
What client-side tools or WASM integrations would you like to see next?
Check out the live app here: https://beepdf.app
Top comments (0)