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:
- Renders a
<ToolLayout>with its title, description, FAQ, and how-it-works steps. - Lazy-loads the WebAssembly engine it needs (only when the user actually clicks Run).
- 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/coreto 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
- 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.
- No account, no cookie, no analytics on tool pages. Session-free by design.
- No dark patterns. No "upgrade to remove watermark," no compressed output that has been re-encoded to justify a paid tier.
- 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.
Zro7