a website that provides a PDF toolkit that runs entirely client-side using WebAssembly, only the AI merge feature sends extracted page text to a server for planning.
picked mupdf as the PDF engine since it compiles to WASM and covers everything needed: merging, splitting, rotating, page manipulation, and rendering to images.
getting mupdf to work with Next.js took some configuration. it uses top level await for WASM initialisation, so webpack needed asyncWebAssembly and topLevelAwait experiments enabled. also had to stub out Node.js built-ins (fs, path, module, etc.) for the browser bundle since mupdf conditionally imports them in Node environments. Next.js uses Turbopack by default but I couldn’t get it working so I moved to webpack via the –webpack flag.
a key detail with mupdf was the buffer copy pattern. buf.asUint8Array() returns a view into the WASM heap, not a real JS buffer. passing it directly to a Blob gives a corrupted or empty file. had to copy the bytes out first with new Uint8Array(src.byteLength).
features built:
- merge: combines multiple PDFs, with an optional AI instruction field that sends extracted page text to Gemini 2.5 Flash via Vercel AI Gateway and gets back a structured page plan
- split: extracts every page into its own PDF, downloaded as a ZIP
- rotate: rotates all or specific pages by 90/180/270 degrees
- remove pages: deletes specific pages by number or range (1, 3, 5-7, etc.)
- extract pages: saves specific pages as a new PDF
- pdf to images: renders each page as a PNG at 2x scale, downloaded as a ZIP
UI is a resizable panel layout, tools on the left and PDF preview iframe on the right. on mobile it stacks vertically. tool picker is a list of items, clicking one shows that tool’s upload UI and action button. button label changes per tool (“merge 3 PDFs”, “split PDF”, etc.)