I want you to try something right now.
Open your favourite online PDF tool, image converter, or background remover. Now turn off your Wi-Fi and try to use it.
Did it work?
If it didn't — and it almost certainly didn't — then that tool is silently doing something with your files that it probably isn't telling you about. Every "instant" cloud tool has a server at the other end. And every server is a potential breach, a data-scraping node, or a jurisdiction you've never heard of.
This is the Airplane Mode Test. If it doesn't pass, it isn't truly private.
At MojoDocs, we built 1,000+ file utilities — PDF compressors, image converters, background removers, deepfake detectors — that all pass this test. Here's the engineering stack that made it possible, and why we think this is the future of developer tooling.
Why "The Cloud Is Secure" Is a Lie We Tell Ourselves
The SaaS surveillance playbook goes like this:
- Offer a free tool that solves a painful problem (PDF merging, image resizing, format conversion).
- Route every file through your server, even if the actual processing takes 200ms.
- While the file is in transit, log metadata: file type, size, content fingerprint, timestamp, IP.
- Optionally train your ML models on user content (with a clause buried 4,000 words into your ToS).
Adobe did this openly in 2024 when they updated their Terms of Service to grant themselves broad rights to scan user content for "machine learning purposes." But most companies do it quietly.
Even if you trust the company — do you trust their:
- Developers who might leave an S3 bucket world-readable?
- Third-party analytics SDK they bundled six months ago?
- Government if served a secret subpoena?
The math is brutal. If you use 10 online tools a month and each stores your file for even 24 hours, that's 3,600 file-hours of exposure per year. Each one is a vector.
The Browser Is a Supercomputer You're Not Using
Here's the thing developers often forget: the browser is insanely powerful now, and we've been offloading work to servers out of habit, not necessity.
The modern browser ships with:
- A multi-threaded JS runtime
- Access to GPU compute
- A full WASM execution environment
- A proper filesystem API
- Native threading via Web Workers
We're running ffmpeg compiled to WASM. We're doing AI inference with ONNX Runtime Web. We're using SharedArrayBuffer for zero-copy data transfer between threads. This isn't "thin client" territory anymore — this is desktop-class compute, in a tab.
Our Stack: What Actually Runs in the Browser
1. WebAssembly for Heavy Lifting
The killer feature. We compile C++ and Rust libraries directly to WASM and ship them as modules. This means:
- Our PDF engine (based on a fork of a popular C++ library) compresses files at ~60MB/second locally
- Format conversion (HEIC → JPG, WebP → PNG, etc.) happens in the render process itself
- Zero round-trip latency — no upload, no queue, no download
// Rough pattern for loading a WASM module
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('/engines/pdf-compress.wasm'),
importObject
);
const result = wasmModule.instance.exports.compress(
inputPtr,
inputLen,
quality
);
The file lives in memory as a Uint8Array. It never serializes to a request body. It never hits a network buffer.
2. Web Workers for Non-Blocking UX
Heavy file ops on the main thread = frozen UI = bad DX. We move all processing into Web Workers:
// main.js
const worker = new Worker('/workers/pdf-worker.js');
worker.postMessage({ file: arrayBuffer, op: 'compress', quality: 80 }, [arrayBuffer]);
worker.onmessage = ({ data }) => {
renderDownloadButton(data.result);
};
// pdf-worker.js
self.onmessage = async ({ data }) => {
const engine = await loadWasmEngine();
const result = engine.process(data.file, data.op, data.quality);
self.postMessage({ result }, [result.buffer]);
};
The [arrayBuffer] in postMessage is a transferable — ownership moves to the worker, no copy. Zero GC pressure.
3. WebGPU for Image AI
For AI-powered features like background removal and deepfake detection, we use ONNX Runtime Web with the WebGPU execution provider. The model runs on your own GPU, not ours.
import * as ort from 'onnxruntime-web';
const session = await ort.InferenceSession.create('/models/bg-remover.onnx', {
executionProviders: ['webgpu'], // Falls back to 'wasm' if unavailable
});
const output = await session.run({ input: tensor });
On a mid-range laptop, background removal runs in under 400ms. Cloud tools that "feel instant" are actually just fast at hiding their loading spinners.
4. File System Access API for Bulk Operations
For power users processing hundreds of files, we use the File System Access API to work directly with a local folder:
const dirHandle = await window.showDirectoryPicker();
for await (const [name, fileHandle] of dirHandle.entries()) {
const file = await fileHandle.getFile();
const processed = await processInWorker(file);
const writable = await fileHandle.createWritable();
await writable.write(processed);
await writable.close();
}
No zip downloads. No batch uploads. Works completely offline. This is what the desktop software experience feels like — in a browser.
The Architecture Trade-offs (Honest Edition)
Local-first isn't free. Here's what you give up:
Bundle size: WASM binaries are chunky. Our PDF engine is ~4MB. We mitigate this with aggressive code-splitting, lazy loading, and a service worker cache so repeat visits are instant.
No server-side telemetry: We genuinely can't see what files users process. Our analytics are limited to page views and click events. If someone encounters a bug mid-processing, we only know if they report it. This forces us to build better error boundaries on the client.
iOS Safari still sucks: WebGPU support on iOS Safari is limited. We fall back to the WASM execution provider for AI features on mobile. Performance degrades gracefully but noticeably.
Cold starts on first visit: First load of the WASM module hits the network. We pre-fetch on idle:
// Prefetch engines during idle time
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
import('/engines/pdf-engine.js'); // Triggers WASM fetch + compile
});
}
Privacy by Architecture Is a Better Guarantee Than Privacy by Policy
Here's the philosophical shift: a privacy policy is a legal document. A local-first architecture is a technical guarantee.
A ToS can be updated overnight. Your browser's sandbox can't. When we tell users "your files never leave your device," that's not a promise — it's a consequence of how the code is written. There's no upload function to compromise.
For developers building tools that handle sensitive data — documents, images, medical files, legal PDFs — this is worth thinking about seriously. Your users are trusting you with their data not because they read your privacy page, but because they don't know what else to do.
Give them a technical reason to trust you instead.
What We're Exploring Next
- Fully client-side LLM inference for document summarisation using WebLLM / llama.cpp compiled to WASM
- P2P file sharing using WebRTC DataChannels — send files directly between browsers without touching a relay server
- Persistent local processing pipelines using the OPFS (Origin Private File System) API for multi-step workflows that survive tab reloads
Try It Yourself
If you want to see this in action, MojoDocs has 1,000+ tools live right now — PDF compressor, merger, image converters, background remover, deepfake detector. Open DevTools, go to the Network tab, and upload a file.
You'll see the WASM module load once. After that, nothing.
No POST requests. No multipart/form-data. No mysterious calls to api.some-cdn.io.
Just your file, your browser, and your CPU doing the work.
The browser has outgrown the "thin client" label. It's time our tooling caught up.
Sachin Sharma is the founder of MojoDocs — a privacy-first, local-first file utility suite. Reach out at support@mojodocs.in.
Top comments (0)