Browser image optimization

jamit-image-optimizer

A framework-independent and fully typed image optimization library for browser uploads. Resize, compress and convert images locally before they reach your upload pipeline.

Built around browser File objects, Web Workers and WebAssembly, with support for mixed-file batches, controlled concurrency, target-size optimization and detailed processing metadata.

Why it exists

Optimize images before the upload starts

Large images increase upload times, bandwidth usage and storage requirements. jamit-image-optimizer adds a dedicated image-processing layer in front of an existing upload flow without taking ownership of the upload itself.

The package accepts normal browser File objects and returns normal File objects. This keeps it compatible with FormData, signed URLs, multipart uploads and custom backend APIs.

Core capabilities

A complete browser-side optimization pipeline

The package focuses on the parts that matter before an image reaches the network while remaining independent from frameworks and backend infrastructure.

File-to-file processing

Pass a browser File to the optimizer and receive a browser File back that can be used directly by an existing upload pipeline.

Local processing

Images are decoded, resized and encoded inside the browser. The package itself does not upload files to an external service.

Web Workers

Expensive image-processing work runs outside the main UI thread to keep applications responsive during optimization.

WebAssembly codecs

JPEG, PNG and WebP processing is powered by browser-compatible WebAssembly codecs bundled into the worker build.

Mixed-file batches

Images and non-image files can be processed together while preserving the original order of the upload selection.

Target-size optimization

JPEG and WebP output can search for a suitable quality level when an approximate target file size is required.

Controlled concurrency

Batch processing limits the number of active image workers to avoid unnecessary CPU and memory pressure.

Abort and status events

AbortSignal support and lifecycle callbacks make it possible to cancel work and reflect processing progress in the UI.

Detailed results

Each operation returns original and output metadata, size savings, compression information and processing timings.

Interactive demo

Try the optimizer directly in your browser

The interactive playground demonstrates real browser-side processing with configurable output modes, quality, resize limits, target size and batch concurrency.

Ready
1

Select files

Drop files here

Images are optimized locally. Other file types can remain unchanged in the same batch.

JPEG · PNG · WebP · native HEIC / HEIF where supported
2

Optimization settings

Processing pipeline

From selected file to upload-ready output

The optimizer follows a predictable processing lifecycle while keeping the surrounding upload implementation completely under application control.

Classify

The input is classified as a supported image, unsupported image or passthrough file using MIME information and file signatures where applicable.

Decode

Supported images are decoded inside the worker using the appropriate codec or native browser capabilities.

Resize

Optional maximum width and height constraints are applied while preserving the aspect ratio and avoiding unnecessary upscaling.

Encode

The image is encoded according to the configured output mode, format, quality and optional target-size strategy.

Finalize

The optimizer decides whether the generated output should replace the original and returns the final File together with detailed metadata.

Installation

Add one package to your frontend

jamit-image-optimizer ships as an ESM package with TypeScript declarations and can be installed with the package manager already used by your project.

pnpm

Shell
pnpm add jamit-image-optimizer

npm

Shell
npm install jamit-image-optimizer

The published package does not require additional runtime dependencies from the consuming application. The codecs used by the worker are bundled into the package build.

Single image API

Start with optimizeImage()

Use optimizeImage() when a single supported image should be resized, compressed or converted before it is uploaded.

TypeScript
import { optimizeImage } from 'jamit-image-optimizer';

const result = await optimizeImage(file, {
  mode: 'auto',
  quality: 0.85,
  resize: {
    maxWidth: 1920,
    maxHeight: 1920,
  },
});

console.log(result.file);
console.log(result.savings.percent);
Upload independent

Keep your existing upload implementation

The optimizer does not send files anywhere. Use the returned File with FormData, fetch, Axios, signed URLs, multipart uploads or any custom backend integration.

TypeScript
const result = await optimizeImage(file, {
  mode: 'auto',
});

const formData = new FormData();

formData.append('file', result.file);

await fetch('/upload', {
  method: 'POST',
  body: formData,
});
Batch processing

Process real-world upload selections

processFiles() is designed for upload interfaces where users select multiple files and the batch may contain both images and unrelated file types.

processFiles()

Optimize multiple files

Supported images are processed with controlled concurrency while the output array keeps the same ordering as the input selection.

TypeScript
import { processFiles } from 'jamit-image-optimizer';

const result = await processFiles(files, {
  mode: 'auto',
  quality: 0.85,
  resize: {
    maxWidth: 1920,
    maxHeight: 1920,
  },
  concurrency: 2,
});

await uploadFiles(result.files);
Mixed files

Images and passthrough files together

PDFs, videos, text files and other non-image files can remain untouched in the same batch while supported images are optimized.

TypeScript
const result = await processFiles([
  photo,
  contractPdf,
  screenshot,
  video,
]);

for (const item of result.items) {
  console.log({
    name: item.originalFile.name,
    kind: item.kind,
    outcome: item.outcome,
  });
}
Output strategy

Choose how the final image format is decided

Three output modes cover format preservation, explicit conversion and automatic optimization without forcing applications into a single strategy.

original

Preserve the original format

The optimizer keeps the source format where an encoder for that format is available and applies resize or compression without intentionally converting it.

TypeScript
const result = await optimizeImage(file, {
  mode: 'original',
  quality: 0.8,
});
format

Request an explicit format

Choose JPEG, PNG or WebP when the application requires a specific output format instead of preserving the source format.

TypeScript
const result = await optimizeImage(file, {
  mode: 'format',
  format: 'webp',
  quality: 0.85,
});
auto

Automatically choose a beneficial output

Auto mode evaluates WebP as an optimization candidate and keeps it only when using the generated output is actually beneficial.

TypeScript
const result = await optimizeImage(file, {
  mode: 'auto',
  quality: 0.85,
});
Resize and compression

Control image dimensions and file size

Resize constraints and quality-based compression can be combined to reduce large camera images before they consume network bandwidth.

Resize without changing the aspect ratio

Define a maximum width, maximum height or both. The image is scaled down to fit the configured bounds without intentional upscaling.

TypeScript
const result = await optimizeImage(file, {
  resize: {
    maxWidth: 1920,
    maxHeight: 1920,
  },
});

Optimize toward a target size

For quality-based output, the optimizer can perform a bounded quality search to approach a target byte size while respecting a configured minimum quality.

TypeScript
const result = await optimizeImage(file, {
  quality: 0.9,
  targetSize: 500 * 1024,
  minQuality: 0.5,
});

console.log(result.compression.targetReached);
Lifecycle control

Connect processing to your application UI

Status callbacks and AbortSignal support make the optimizer suitable for interactive upload interfaces instead of only background scripts.

Observe processing stages

Receive lifecycle updates such as decoding, resizing, encoding and finalizing so the application can expose meaningful processing states.

TypeScript
await optimizeImage(file, {
  onStatus(status) {
    console.log(status.stage);
  },
});

Cancel active processing

Pass an AbortSignal to stop an active operation. Running workers are terminated instead of continuing unnecessary processing in the background.

TypeScript
const controller = new AbortController();

const promise = processFiles(files, {
  concurrency: 2,
  signal: controller.signal,
});

controller.abort();

await promise;
HEIC / HEIF

Use native decoding when the browser provides it

HEIC and HEIF input support intentionally relies on native browser and operating-system decoding capabilities. The package does not bundle its own HEVC decoder.

When the environment can decode the file, it can be converted to an encodable output such as WebP, JPEG or PNG. When native decoding is unavailable, the package reports the unsupported codec path instead of pretending the format can be processed.

Resource protection

Define deterministic processing limits

Large compressed images can expand dramatically when decoded. Configurable input-size, pixel-count and dimension limits allow applications to prevent unexpectedly expensive browser-side processing.

TypeScript
const result = await optimizeImage(file, {
  limits: {
    maxInputBytes: 20 * 1024 * 1024,
    maxPixels: 12_000_000,
    maxDimension: 8192,
  },
});
Typed errors

Handle failures through stable error codes

ImageOptimizerError exposes typed error codes for conditions such as invalid options, unavailable codecs, unsupported output formats, resource limits, worker failures and aborted operations.

TypeScript
import {
  isImageOptimizerError,
  optimizeImage,
} from 'jamit-image-optimizer';

try {
  await optimizeImage(file);
} catch (error) {
  if (isImageOptimizerError(error)) {
    console.error(error.code);
    console.error(error.message);
  }
}
Framework independent

Use the same core across frontend stacks

The public API is plain TypeScript and works with browser File objects. Vue, Nuxt, React, Next.js, Svelte and Vanilla TypeScript applications can use the same package without a framework-specific adapter.

  • Vanilla TypeScript
  • Vue
  • Nuxt
  • React
  • Next.js
  • Svelte
Local by design

The optimizer does not upload your images

Image processing happens locally in the browser until the surrounding application decides what to do with the returned File.

jamit-image-optimizer does not send images, filenames, optimization metadata or processing telemetry to a JamIT service. The application's own upload or analytics infrastructure remains separate from the package.

Browser capabilities

Built for modern browser environments

The processing API depends on modern browser capabilities. HEIC and HEIF decoding additionally depends on the native codec support available in the browser and operating system.

  • File API
  • Web Workers
  • WebAssembly
  • JPEG / PNG / WebP
  • Native HEIC / HEIF where available
Package status

Published and ready to integrate

The first public version includes single-image optimization, mixed-file batch processing, JPEG, PNG and WebP codecs, resize and quality controls, target-size optimization, native HEIC/HEIF decoding where available, controlled concurrency, abort support and detailed result metadata.

The package is published on npm and the source code, tests and development playground are available publicly on GitHub.

Published
Open source

Explore the source on GitHub

Review the implementation, browser tests, worker pipeline and public API, or report an issue directly in the repository.

Open GitHub repository