skip to content
project archive / case file

Paperkit

Your PDF never leaves the tab it's opened in.

shippedSolo build — product, architecture, 34 toolsProof-of-work project
image coming soon
Paperkit — hero shot

34

PDF tools

6

tool categories

0

files uploaded to a server

1

hook powering every tool

01 / the real problem
01

"free" PDF tools aren't free

most sites trade the missing price tag for your document — it gets uploaded to a server you can't see, just to merge two pages.

02

sensitive files, public servers

a contract, an ID, a bank statement — the moment it touches someone else's backend, you're trusting infrastructure you have no visibility into.

03

watermarks and sign-up walls

the tools that don't upload your file often gate the output behind an account, a subscription, or a stamped-on logo.

02 / the approachbuild log, in order
  1. no server in the data path, by architecture

    every tool — merge, compress, OCR, encrypt — runs client-side via WebAssembly and browser APIs. there's no upload step because there's no endpoint to upload to.

  2. one hook powers all 34 tools

    usePdfTool() owns file state, the idle → processing → done → error lifecycle, and progress. a new tool is "render the shell, wire one processing function" — not a rewrite.

  3. processing logic knows nothing about React

    lib/pdf, lib/convert, and lib/ai are pure File[] → Blob functions with zero UI dependency, so they're trivially testable and reusable outside the hook.

  4. a single tool registry drives everything

    config/tools.ts lists slug, name, description, icon, and category once — the homepage grid, search, and sitemap all read from that one file.

  5. AI is the one deliberate exception

    Chat, Summarize, and Compare send extracted text — never the source file — to Gemini through a serverless proxy that keeps the API key off the client.

  6. installable, works offline

    ships as a PWA. install once, and every tool except the AI ones keeps working with no connection at all.

03 / how it's built
frontend
Next.js 15 (App Router), TypeScript, Tailwind CSS v4
animation
Framer Motion
pdf core
pdf-lib (create/modify) + pdfjs-dist (render/parse)
ocr
Tesseract.js — in-browser, WebAssembly
office formats
Mammoth, docx, SheetJS (xlsx)
generation
jsPDF + html2canvas
ai
Google Gemini via a serverless proxy route
deployment
Vercel — static-first, edge-served
04 / what it doesclick a drawer

05 / a few screensimages land here
image coming soon
homepage — tool grid, search, and categories
image coming soon
merge tool — dropzone, reorder, process
image coming soon
OCR tool — running fully in-browser
image coming soon
chat with pdf — ai proxy in action
06 / why, not just what

why client-side over a backend that just deletes files after?

"we delete it after" is a policy, not a guarantee — it still requires trusting a server you can't inspect. running entirely in the browser removes the trust requirement instead of asking for it.

why one usePdfTool hook instead of per-tool state?

34 tools sharing the same upload → process → download shape means the lifecycle only needs to be built, and debugged, once.

why send extracted text to Gemini instead of the PDF itself?

the AI features are genuinely useful, but they don't need the source file — only its text. sending the minimum necessary data keeps the "never leaves your device" promise honest for the other 31 tools.

why pdf-lib and pdfjs-dist instead of a single library?

pdf-lib is built for creating and modifying documents; pdfjs-dist is built for rendering and parsing them. using one for each job beats forcing one library to do both awkwardly.

that's the whole file← back to the archive