# Running PDF, OCR and AI tools entirely in the browser: what worked and what broke

> Source: <https://dev.to/edward_pounders_dd56b63ed/running-pdf-ocr-and-ai-tools-entirely-in-the-browser-what-worked-and-what-broke-3dmg>
> Published: 2026-11-13 19:43:30+00:00

Most "free online PDF tools" upload your file to a server, process it there, and delete it an hour or two later. I wanted the opposite for [Utilkits](https://utilkits.com): 99 everyday tools, where the PDF and image tools keep the file on your device. Here is how the browser does the heavy lifting, where it falls short, and the two bugs that taught me the most.

It's a Next.js 16 app, and the PDF, image and on-device AI tools run client-side:

| Job | Library | 
|---|---|
| Merge, split, rotate, watermark, page numbers | pdf-lib | 
| Render pages, extract text, PDF to JPG/PNG | pdf.js (pdfjs-dist) | 
| Password-protect and unlock (AES-256) | qpdf compiled to WebAssembly (qpdf-wasm) | 
| OCR | Tesseract.js, in a web worker | 
| PDF to Word / Excel / PowerPoint | docx, SheetJS (xlsx), PptxGenJS, rebuilt from the extracted text | 
| Image compression and conversion | browser-image-compression and canvas | 
| On-device AI | Transformers.js with onnxruntime-web (WebAssembly) | 

The rule I hold myself to: if a tool can't run locally, the page says so. The redirect checker has to follow redirects from a server, so the [how it works](https://utilkits.com/how-it-works) page lists it as an exception.

`BritishWerewolf/U-2-Netp`, about 4.6 MB), run in a web worker so the page never freezes.`onnx-community/whisper-tiny.en`).` Xenova/distilbart-cnn-6-6`).` all-MiniLM-L6-v2`, about 24 MB) finds the passages closest to the question. A DistilBERT trained on SQuAD (about 66 MB) then quotes the answer, with its page number. It quotes rather than generates, so it can't invent a citation. If it isn't confident, it says so and shows the closest passages.
The models download once from Hugging Face and the browser caches them. The files themselves never leave the device.

The AI tools worked locally and failed in production on first use. The self-hosted `transformers.web.min.js` imported `onnxruntime-web/webgpu` by bare name, and no browser can resolve a bare module name without an import map. On top of that, the library's default is to fetch the onnxruntime `.wasm` engine from jsDelivr, which my content security policy blocks.

The fix: load `transformers.min.js` instead, copy the onnxruntime files from `node_modules` into `/transformers/` before every build, and point `env.backends.onnx.wasm.wasmPaths` at that folder.

That fix had its own trap. **Set `wasmPaths` on the existing settings object; don't replace the object.** The runtime keeps a reference to the original, so assigning a new object does nothing and the engine still comes from the CDN.

The background remover also "worked" in development and never in production. Transformers.js 4 has no `U2NetImageProcessor`, and my code read an output name the model doesn't have.

Now the worker prepares the input itself:

`preprocessor_config.json`.` input.1`, and read its real saliency map, output `1959`.
The preparation is a set of pure functions, so the same code runs in the worker and in Node unit tests.

"Your file stays on your device" is easy to say, so every tool page has a **privacy badge**:

`fetch`, `XMLHttpRequest` and `navigator.sendBeacon` to block the page's own requests, and sets Google Analytics' official `ga-disable` flag.
The badge also suggests the real test: load the page, turn off Wi-Fi, run the tool. If it still works, your file had nowhere to go.

Everything is at [utilkits.com](https://utilkits.com). The single-file tools are free, with no account. If you catch a tool sending your file anywhere, tell me in the comments.
