Building a Client-Side
PDF Engine
How ILikePdf processes 150 MB+ documents entirely in your browser — zero server uploads, zero privacy exposure — using WebAssembly, intelligent memory management, and device-adaptive processing.
01 — The Challenge
Processing PDFs in the browser is notoriously difficult. Traditional web apps upload files to servers, process them remotely, and return results. This approach has critical flaws for privacy-sensitive documents: contracts, medical records, financial statements.
The core problem — PDFs are complex binary formats requiring significant RAM. A 50 MB PDF with images needs 200–300 MB to process. Multiply across concurrent users and server costs skyrocket. More importantly, sensitive documents shouldn't leave your device at all.
✕ Server-Side
- Privacy risk — files travel over the network
- Upload/download bandwidth waste
- Server costs and scaling problems
- Network latency on every operation
- Single point of failure for breaches
✓ Client-Side
- Complete privacy — bytes never leave device
- Instant — no network round-trip
- Zero infrastructure cost
- Works fully offline after first load
- No server = no server breach possible
02 — Technical Architecture
Core Libraries
Two WebAssembly-powered engines handle all processing. Both run entirely inside the browser tab — no native installation, no server dependency.
pdf-lib — v1.17.1
PDF manipulation engine. Merges, splits, adds pages, edits metadata. Works at the PDF structure level — copies pages without re-rendering, making merges instant.
Handles PDF internals: form fields, annotations, encryption.
pdf.js — v3.11.174
Mozilla's PDF rendering engine — the same one Firefox uses natively. Converts pages to Canvas for pixel-perfect previews and high-DPI image export.
Worker architecture offloads heavy parsing to a separate thread.
Storage Strategy
Three-tier storage solves different problems. The key rule: keep data as ArrayBuffer as long as possible — converting to strings increases memory 3–5×.
const dbSet = async (key, val) => {
const db = await initDB();
const tx = db.transaction('ultimatepdf-store', 'readwrite');
tx.objectStore('ultimatepdf-store').put(val, key);
return new Promise((res, rej) => { tx.oncomplete = () => res(); tx.onerror = () => rej(tx.error); });
};
// Tier 2: localStorage — metadata only (no file bytes)
localStorage.setItem('ultimatepdf_history', JSON.stringify(history));
// Tier 3: RAM — active processing (volatile)
const pdfDoc = await PDFDocument.load(arrayBuffer);
03 — Memory Management
The app dynamically adjusts limits based on device type and available memory. A 4 GB RAM phone typically has 1–1.5 GB available for browser tabs. A single 100 MB PDF can consume 300–400 MB during processing.
const isMobile = /Android|iPhone/i.test(navigator.userAgent);
const deviceMem = navigator.deviceMemory || 4;
if (isMobile && screen.width < 768) {
return { maxFileSize: 50 * 1024 * 1024, maxDPI: 300, maxPagesPerBatch: 10 };
}
if (deviceMem < 4) {
return { maxFileSize: 100 * 1024 * 1024, maxDPI: 450, maxPagesPerBatch: 30 };
}
return { maxFileSize: 150 * 1024 * 1024, maxDPI: 600, maxPagesPerBatch: 50 };
};
| Device | Max File | Max DPI | Batch Size |
|---|---|---|---|
| Smartphone | 50 MB | 300 | 10 pages |
| Tablet | 75 MB | 450 | 25 pages |
| Desktop | 150 MB | 600 | 50 pages |
const estimateMemoryUsage = (fileSize, pageCount, scale, format) => {
const basePerPage = 5 * 1024 * 1024;
const scaleFactor = Math.pow(scale, 2);
const fmtMultiplier = format === 'png' ? 1.5 : 1.0;
return { estimated: pageCount * basePerPage * scaleFactor * fmtMultiplier };
};
04 — Security & Privacy
ILikePdf operates on a simple principle: we can't leak what we never see. Files never touch our servers, third-party APIs, or external services. All processing happens in the browser's sandboxed environment.
FileReader.readAsArrayBuffer(file)
→ PDFDocument.load(arrayBuffer)
→ pdfDoc.save()
→ new Blob([bytes])
→ URL.createObjectURL(blob)
→ anchor.click()
// No network request anywhere in this chain
Offline-First via Service Worker
After the first page load, all tools function without any internet connection. Enable airplane mode and process PDFs normally — the WebAssembly libraries are cached locally.
self.addEventListener('install', (e) => {
e.waitUntil(caches.open('ultimatepdf-v1').then(c => c.addAll(['/', ...ASSETS])));
});
self.addEventListener('fetch', (e) => {
e.respondWith(caches.match(e.request).then(cached => cached || fetch(e.request)));
});
ILikePdf — Engineering: Performance · Privacy · Accessibility