Engineering11 min

How We Built 90+ File Tools That Never Upload Your Data

The architecture behind Zro7: how one browser shell hosts FFmpeg, DuckDB, Tesseract, pdf-lib, OpenCascade and dozens more WebAssembly engines — with zero server-side processing.

Everything described here runs on your device. Zro7 has no upload endpoint.

Zro7 ships more than 90 file-processing tools — PDFs, images, video, audio, SQL, OCR, archives, 3D — and none of them upload your file to a server. This post is the honest engineering write-up: what the shell looks like, which WebAssembly modules we lean on, and where the tricky parts are.

The shell

There's one shared React shell (built on TanStack Start) that hosts every tool. It handles routing, the file dropzone, the progress UI, telemetry-free error boundaries, i18n, and the SEO metadata. A tool page is a thin component that:

  1. Renders a <ToolLayout> with its title, description, FAQ, and how-it-works steps.
  2. Lazy-loads the WebAssembly engine it needs (only when the user actually clicks Run).
  3. Wires up a small pure function: (File[], options) => Promise<File[]>.

That's the whole abstraction. It keeps the network zero and the code split small.

The engines

  • pdf-lib + @cantoo/pdf-lib — merge, split, watermark, encrypt, form flatten, page rotate. Pure JavaScript, no WASM needed.
  • pdf.js — rasterize PDF pages for OCR and thumbnails.
  • FFmpeg.wasm 0.12 — every video and audio tool. We pin @ffmpeg/core to 0.12.10 for stable worker resolution.
  • DuckDB-WASM — SQL on CSV/Parquet/JSON, streamed from a File handle.
  • sql.js — open and browse SQLite databases.
  • Tesseract.js — OCR for image and PDF pages.
  • @imgly/background-removal — client-side segmentation model.
  • heic-to, exifr, browser-image-compression, piexifjs — image conversions and metadata.
  • libarchive.js, @zip.js/zip.js, fflate — extract 10+ archive formats and create AES-256 ZIPs.
  • three.js + occt-import-js + meshoptimizer — STL / STEP / OBJ / glTF viewing and mesh simplification.
  • Web APIs — MediaRecorder for screen/webcam/voice recording, Web Audio for the tuner and metronome, Canvas for the whiteboard.

The rules we hold ourselves to

  1. No outbound network request carries user file bytes. The only allowed outbound requests are the app shell, WASM binaries, and CDN font/icon assets. You can verify in DevTools.
  2. No account, no cookie, no analytics on tool pages. Session-free by design.
  3. No dark patterns. No "upgrade to remove watermark," no compressed output that has been re-encoded to justify a paid tier.
  4. Every tool is a durable URL. /compress-pdf, /mp4-to-gif, /dns-lookup. No hash fragments, no query-only routing.

The hard parts

Worker + WASM resolution

Every heavy engine runs in a Web Worker so the UI stays responsive. Getting Vite to emit the worker script + .wasm asset with correct relative URLs was the single biggest time sink. Pinning ffmpeg-core to a matching minor version fixed the recurring "can't resolve worker" errors in production.

Streaming very large files

For a 4 GB CSV we can't call file.arrayBuffer() — that would OOM the tab. Instead we hand DuckDB the FileSystemFileHandle so it can stream. Same idea for FFmpeg: process frames as they arrive, don't buffer the whole video.

Cross-origin isolation

Anything that uses SharedArrayBuffer (threaded ffmpeg-core, some tesseract builds) requires COOP + COEP headers. We set them at the edge for every tool page.

First-load weight

A tool page that pulls in FFmpeg would be a 30 MB first-paint disaster. We only fetch the engine after the user clicks Run, and we cache aggressively so the second visit is instant.

What we deliberately did not build

  • A user account system — because we don't need one.
  • Server-side file storage — same reason.
  • Analytics on tool pages — telemetry is the opposite of the pitch.
  • A queue for long jobs — the browser is the queue.

What's next

More CAD/3D formats, better on-device image models, richer DuckDB dashboards, and per-tool benchmarks against cloud alternatives. Same rule: it ships when it runs entirely in the browser.

Frequently asked questions

How big is the total download?

The shell is a few hundred KB. Individual engines (FFmpeg ~30 MB, Tesseract ~15 MB, DuckDB ~10 MB) are only fetched when you use the tool that needs them, and then cached.

Do you use any server for anything?

Only to serve the static app shell and WebAssembly binaries. Nothing user-provided is sent.

Is the code open?

Individual engines (FFmpeg, DuckDB, Tesseract, pdf-lib, etc.) are open source. The shell is not, but the privacy claim is verifiable at runtime — DevTools tells you the truth.

Can I run Zro7 offline forever?

Yes, once a tool page's assets are cached. Some engines are large enough to be evicted after long inactivity, in which case they re-download on next visit.

Related posts

← Back to blog