DEV Community

Gaurav Bhowmick
Gaurav Bhowmick

Posted on

I replaced server-side image compression with 40 lines of Canvas API code

I was building a side project that needed image compression. My first instinct was to look for an API — TinyPNG, Cloudinary, something with a POST endpoint.
Then I looked at the pricing. And the rate limits. And the fact that I’d be uploading user photos to someone else’s infrastructure.
So I tried something different: what if the browser just… did it?
Turns out the Canvas API can compress images surprisingly well. The core logic is about 40 lines:
const fallbacks = [0.6, 0.45, 0.3, 0.2];
let blob = await compress(img, quality, format);

if (blob.size >= file.size) {
for (const fq of fallbacks) {
const attempt = await compress(img, fq, format);
if (attempt.size < file.size) {
blob = attempt;
break;
}
}
}

Try the requested quality first. If the output is bigger, walk down through lower qualities until something sticks. As a last resort, if WebP output is still larger, fall back to JPEG entirely.
Not elegant, but it works on basically everything I’ve thrown at it.
A side effect I didn’t expect
The Canvas API strips all EXIF metadata. When you draw an image to canvas and export it, the output contains zero metadata — no GPS coordinates, no device model, no timestamps.
I originally considered this a bug. Then I realized most people compressing passport photos and ID scans probably want that metadata gone. So I kept it as a feature.
What about PDFs?
I needed PDF-to-image conversion for a separate use case and figured I’d add it to the same tool. pdfjs-dist handles the rendering, jsPDF handles the reverse direction. Both run client-side.
The tricky part was pdfjs-dist v5 breaking everything. Downgraded to v3.11.174 and it worked immediately. Also had to add canvas: false to the webpack config because pdfjs tries to import the Node canvas module during build.
The result
I bundled all of this into a tool called MiniPx (minipx.com). The whole thing is a static Next.js export on Netlify. No backend. Server cost is literally zero.
Features that exist because I kept needing them:
• Multi-pass compression with format fallback
• WebP/JPEG/PNG conversion
• Resize with presets (passport, email, WhatsApp)
• EXIF stripping (automatic)
• PDF to image and image to PDF
• Batch ZIP download via jszip
The entire homepage is one React component at ~400 lines. Heavy libraries (pdfjs, jspdf, jszip) are lazy-loaded so they don’t affect initial page load.
Not saying this replaces server-side compression for production pipelines. But for a utility tool where users compress a few photos? The browser handles it fine.
Source of the compression approach is just the standard Canvas API — no special libraries needed for the image part. The interesting engineering was mostly in the fallback logic and making sure memory gets cleaned up (revokeObjectURL after every operation, reset canvas dimensions after PDF rendering).
If you’re building something similar, the main gotchas:
1. Always check if output > input. Canvas compression can make files larger.
2. JPEG needs a white background fill before drawImage, or transparent areas turn black.
3. Mobile browsers have canvas size limits. Cap your render scale.
4. pdfjs-dist v5 is broken for client-side use. Stick with v3.
Happy to answer questions if anyone’s doing something similar.

Top comments (0)