ZynU Image Compressor is a fully client-side tool built on Next.js 16 and React 19. Drop any image, pick a format and quality, and get a compressed file — all without a single byte of your data touching a server.
Every feature runs entirely in the browser — no server, no API, no account.
Compression runs off the main thread — the UI never freezes, even for large batches.
No network requests for image data. Nothing leaves the browser. Zero tracking.
JPEG, JPG, PNG, WebP, BMP, AVIF — convert between any of them with one click.
Upload and compress unlimited images, then download everything as a single ZIP.
Set target width and height in pixels. Aspect ratio is always preserved.
All processing is client-side. No API calls, no compute bills, no server needed.
From file drop to download — nothing ever hits a network endpoint.
User drags images onto the drop zone or opens a file picker. File objects go into React state. URL.createObjectURL() generates preview URLs — no file data is read until compression starts.
Pick output format (JPEG / PNG / WebP / BMP / AVIF), adjust the quality slider (10–100%), and optionally set max width or height in pixels. All state is local — no server sync.
The library spawns a Worker thread. It re-encodes the image on an HTML Canvas using initialQuality as the quality parameter. alwaysKeepResolution: true prevents unexpected downscaling when no resize is set.
Single file: object URL + anchor click. Batch: JSZip assembles all blobs into one archive, then the same anchor mechanism triggers download. All object URLs are revoked after use.
The browser-image-compressionlibrary exposes several knobs. Here's exactly how ZynU configures them — and why the original code had a significant bug.
maxSizeMB: quality / 100. That option is a file-size ceiling in megabytes — not a quality control. At quality 80, it set a 0.8 MB ceiling. A 50 KB photo would barely be touched; a 5 MB photo would be violently crushed to exactly 800 KB regardless of visual quality. The fix: wire the slider to initialQuality instead.// compressImage() — the core logic const options = { // File-size ceiling — set to original size so the library // never force-crushes beyond what initialQuality produces. maxSizeMB: imageData.original.size / (1024 * 1024), // ← THE real quality knob for JPEG / WebP / AVIF (0–1). // This is what the slider controls. Common mistake: using // maxSizeMB as a quality proxy — that only caps file size. initialQuality: quality / 100, // Prevent silent downscaling unless user sets px dimensions. alwaysKeepResolution: !hasResize, useWebWorker: true, // non-blocking — UI stays responsive fileType: `image/${outputFormat}`, ...(hasResize && { maxWidthOrHeight: Math.max(Number(width) || 0, Number(height) || 0) }), }
initialQuality affects JPEG, WebP, and AVIF output. PNG and BMP are lossless by spec — the slider has no effect when those formats are selected.// Single file download — object URL + anchor click const url = URL.createObjectURL(imageData.compressed) const a = document.createElement('a') a.href = url a.download = `${name}_compressed.${outputFormat}` document.body.appendChild(a); a.click() URL.revokeObjectURL(url) // clean up — prevents memory leak // Batch ZIP — JSZip bundles all compressed blobs const zip = new JSZip() images.forEach(img => zip.file(`${name}.${fmt}`, img.compressed)) const blob = await zip.generateAsync({ type: 'blob' })
Migrated from Next.js 14 → 16. Every dependency verified against official release notes.
| Package | Version | Role |
|---|---|---|
| next | ^16.2.1 | Framework — App Router, Turbopack (default), React Server Components |
| react / react-dom | ^19.2.0 | UI runtime — React 19 bundled with Next.js 16 |
| browser-image-compression | ^2.0.2 | Core engine — Web Worker, initialQuality, alwaysKeepResolution |
| jszip | ^3.10.1 | Batch download — assembles compressed blobs into a ZIP |
| lucide-react | ^0.577.0 | Icons — upgraded from 0.263.1 to support React 19 peer dep |
| tailwindcss | ^3.4.17 | Utility CSS with custom font stack and dark design system |
| typescript | ^5 | Strict typing — minimum 5.1.0 required by Next.js 16 |
vercel.json and netlify.tomlare pre-configured with NODE_VERSION = "20".Yes — completely free, unlimited compressions, no account, no hidden fees. All processing is static; there's no per-use server cost.
No. Everything runs in your browser via the Web Workers API and HTML Canvas. Your images never leave your device.
Input: any image your browser can decode. Output: JPEG, JPG, PNG, WebP, BMP, AVIF. Quality control (initialQuality) only affects lossy formats — PNG and BMP are lossless.
The original code used maxSizeMB: quality / 100 — treating a file-size ceiling as a quality control. This causes erratic results. The fix maps the slider to initialQuality (0–1), which is the actual encoding quality parameter for JPEG / WebP / AVIF.
Yes. Upload as many as you like, compress all at once with the Convert All button, then download everything as a single ZIP.
Node.js 20.9.0 or higher. Next.js 16 dropped support for Node 18. Both vercel.json and netlify.toml are pre-configured with Node 20.
Full Next.js 16 source code. Works on Vercel and Netlify out of the box. No backend, no API keys, no subscriptions.