zynu.netfreeimage-compressor
Free Tool · Full Source Code · Self-hostable

Compress images.
In the browser. Privately.

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.

Next.js 16.2.1React 19.2TypeScript 5Turbopack100% free0 server calls
traf-img-compress.vercel.app — Live Demo
🖼
Drop images here · or click to browse
JPG · PNG · WEBP · BMP · AVIF
Format
JPEG
Quality
80%
Width px
Auto
Height px
Auto
⚡ Convert All
↓ Download ZIP
✕ Clear All
6
Output Formats
Images per session
0
Server uploads
0
Account required

What it does

Every feature runs entirely in the browser — no server, no API, no account.

Web Worker Processing

Compression runs off the main thread — the UI never freezes, even for large batches.

🔒

100% Private

No network requests for image data. Nothing leaves the browser. Zero tracking.

🗂

6 Output Formats

JPEG, JPG, PNG, WebP, BMP, AVIF — convert between any of them with one click.

📦

Batch + ZIP

Upload and compress unlimited images, then download everything as a single ZIP.

📐

Custom Resize

Set target width and height in pixels. Aspect ratio is always preserved.

🆓

Zero Cost to Run

All processing is client-side. No API calls, no compute bills, no server needed.

How it works

From file drop to download — nothing ever hits a network endpoint.

01

Drop or Select Images

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.

02

Configure Settings

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.

03

Compress via Web Worker

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.

04

Download

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.

Compression options — explained

The browser-image-compressionlibrary exposes several knobs. Here's exactly how ZynU configures them — and why the original code had a significant bug.

⚠️
Bug in the original code: The quality slider was wired to 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.
app/page.tsx — compressImage()
// 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)
  }),
}
Quality control only works on lossy formats. initialQuality affects JPEG, WebP, and AVIF output. PNG and BMP are lossless by spec — the slider has no effect when those formats are selected.
app/page.tsx — download helpers
// 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' })

Technology stack

Migrated from Next.js 14 → 16. Every dependency verified against official release notes.

PackageVersionRole
next^16.2.1Framework — App Router, Turbopack (default), React Server Components
react / react-dom^19.2.0UI runtime — React 19 bundled with Next.js 16
browser-image-compression^2.0.2Core engine — Web Worker, initialQuality, alwaysKeepResolution
jszip^3.10.1Batch download — assembles compressed blobs into a ZIP
lucide-react^0.577.0Icons — upgraded from 0.263.1 to support React 19 peer dep
tailwindcss^3.4.17Utility CSS with custom font stack and dark design system
typescript^5Strict typing — minimum 5.1.0 required by Next.js 16
ℹ️
Node.js 20.9+ required. Next.js 16 dropped Node 18. Both vercel.json and netlify.tomlare pre-configured with NODE_VERSION = "20".

Common questions

Is it free?

Yes — completely free, unlimited compressions, no account, no hidden fees. All processing is static; there's no per-use server cost.

Are my images uploaded to a server?

No. Everything runs in your browser via the Web Workers API and HTML Canvas. Your images never leave your device.

Which formats are supported?

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.

Why did the quality slider not work properly before?

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.

Can I compress multiple images?

Yes. Upload as many as you like, compress all at once with the Convert All button, then download everything as a single ZIP.

What Node.js version does deployment require?

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.

Deploy it yourself — free, forever.

Full Next.js 16 source code. Works on Vercel and Netlify out of the box. No backend, no API keys, no subscriptions.