The Problem: 30 Million Students, One Frustrating Error
Every year, over 30 million Indian students fill government exam application forms for SSC, IBPS, UPSC, and Railways. And every year, thousands get their applications rejected because of one thing: document upload errors.
"File size exceeded." "Invalid format." "Dimensions incorrect."
These errors appear at the final submission step — after students have spent hours filling forms. The only solution most know? Visit a cyber cafe and pay someone to resize their Aadhaar-linked photo on a random website.
That's a privacy nightmare. So I built ReadyToSubmit — a suite of document tools where everything runs in your browser.
Architecture: Zero Server File Handling
The core architectural decision was simple: no file should ever leave the user's device.
Here's how we achieved it:
User's Browser
├── File API (input)
├── Canvas API (image processing)
├── pdf-lib (PDF manipulation)
├── jsPDF (PDF creation)
├── pdfjs-dist (PDF rendering)
└── Blob API (output/download)
Image Compression — Canvas API
// Compress using HTML5 Canvas — runs 100% in browser
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = targetWidth;
canvas.height = targetHeight;
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
// Quality parameter controls compression (0.0 to 1.0)
canvas.toBlob(
(blob) => {
const downloadUrl = URL.createObjectURL(blob);
// Trigger download — no server involved
},
"image/jpeg",
quality
);
The key insight: canvas.toBlob() with quality parameter handles JPEG compression natively. No need for any server-side image processing library.
PDF Operations — pdf-lib
import { PDFDocument } from "pdf-lib";
// Merge PDFs — entirely in browser memory
const mergedPdf = await PDFDocument.create();
for (const file of files) {
const bytes = await file.arrayBuffer();
const pdf = await PDFDocument.load(bytes);
const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
pages.forEach((page) => mergedPdf.addPage(page));
}
const mergedBytes = await mergedPdf.save();
const blob = new Blob([mergedBytes], { type: "application/pdf" });
pdf-lib is a pure JavaScript library — no native dependencies, no WASM, no server calls. It manipulates PDFs entirely in memory.
Security: Magic Byte Validation
We don't just check file extensions — we validate the actual binary signature:
const FILE_SIGNATURES = {
"image/jpeg": [
[0xFF, 0xD8, 0xFF, 0xE0], // JFIF
[0xFF, 0xD8, 0xFF, 0xE1], // EXIF
],
"application/pdf": [
[0x25, 0x50, 0x44, 0x46, 0x2D], // %PDF-
],
};
// Read first 16 bytes and compare against known signatures
const bytes = await readFileBytes(file, 16);
This prevents file extension spoofing attacks — a .jpg file containing malicious content won't pass our validator.
The Verification: Is It Really Client-Side Only?
I ran a full security audit on the codebase. Here's what I searched for:
| Pattern | Found? |
|---|---|
fetch() |
❌ Not found |
axios |
❌ Not installed |
XMLHttpRequest |
❌ Not found |
POST requests |
❌ Not found |
/api routes |
❌ Directory doesn't exist |
| Server actions | ❌ Not found |
| Cloud storage calls | ❌ Not found |
Zero network calls for file processing. The only external connections are Google Analytics (page views only) and Google Fonts.
Tech Stack
- Framework: Next.js 15 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS 4
- Image Processing: HTML5 Canvas API
- PDF Processing: pdf-lib, jsPDF, pdfjs-dist
- Hosting: Vercel
- Analytics: Google Analytics GA4
What I Learned
Canvas API is incredibly powerful — Image compression, format conversion, DPI checking, background detection — all possible without any npm package.
pdf-lib is underrated — Pure JS PDF manipulation that works in browsers, Node.js, and Deno. No native dependencies.
Privacy isn't just a feature, it's architecture — Once you decide "no file leaves the browser," every technical decision becomes clearer.
Specificity wins over generality — Instead of building "another PDF tool," I built "the tool that tells you exactly why SSC rejected your photo." The specificity is the moat.
Try It
🔗 ReadyToSubmit — 100% Free, No Login, No Uploads
I'd love feedback from the dev community:
- Is the client-side-only approach sustainable as tools get more complex?
- Would you use a privacy-first approach for your own projects?
- Any suggestions for additional tools?
Drop a comment below! 👇
Top comments (0)