ScreenshotNeo

BlogHow-to

How to Compress Images in React

Compress a selected image in React before upload with browser-image-compression or browser APIs, with runnable code, options, and troubleshooting.

By the ScreenshotNeo team4 October 20268 min read

To compress an image before uploading it in React, read the selected File, process it asynchronously in the browser, then upload the returned File or Blob. React does not encode images itself. A library such as browser-image-compression handles the encoding and offers worker processing, progress, and cancellation options.

Install the package

npm install browser-image-compression

See the package documentation for current options and compatibility details.

Compress and upload a selected image

This complete component compresses a selected image, checks whether the result is actually smaller, and uploads the resulting file with FormData. Replace the example upload endpoint with your application’s endpoint.

import { useState } from 'react';
import imageCompression from 'browser-image-compression';

export default function ImageUploader() {
  const [status, setStatus] = useState('');
  const [progress, setProgress] = useState(0);

  async function handleFileChange(event) {
    const original = event.target.files?.[0];
    if (!original) return;

    setStatus('Compressing…');
    setProgress(0);

    try {
      const options = {
        maxSizeMB: 1,
        maxWidthOrHeight: 1920,
        useWebWorker: true,
        onProgress: (percent) => setProgress(percent),
      };

      const compressed = await imageCompression(original, options);
      // Compression is not guaranteed to reduce every input. Keep the smaller file.
      const uploadFile = compressed.size < original.size ? compressed : original;

      const form = new FormData();
      form.append('image', uploadFile, uploadFile.name);

      const response = await fetch('/api/upload', {
        method: 'POST',
        body: form,
      });
      if (!response.ok) throw new Error(`Upload failed (${response.status})`);

      setStatus(`Uploaded ${uploadFile.name}`);
    } catch (error) {
      if (error?.name === 'AbortError') {
        setStatus('Compression cancelled.');
      } else {
        setStatus(error instanceof Error ? error.message : 'Image processing failed.');
      }
    } finally {
      // Let the same file be selected again after success or failure.
      event.target.value = '';
    }
  }

  return (
    <div>
      <label>
        Choose an image
        <input type="file" accept="image/*" onChange={handleFileChange} />
      </label>
      {progress > 0 && <progress value={progress} max="100" />}
      <p role="status">{status}</p>
    </div>
  );
}

The size and dimension values are examples, not universal targets. Choose them to match your upload limits and visual requirements. The server should still validate uploaded content and enforce its own size and type limits; client-side processing is not a security boundary.

Choose compression settings

Byte size and pixel dimensions control different things. A byte-size target asks the encoder to reduce file storage; a dimension cap reduces the number of pixels that need to be represented. Use the smallest set of constraints that satisfies the product.

Option Use Tradeoff
maxSizeMB Set a target maximum output size in megabytes. Meeting a strict target can require lower quality or smaller dimensions; inspect the output for your content.
maxWidthOrHeight Cap the longer dimension while retaining the image’s aspect ratio. Useful when uploads do not need original resolution. It does not by itself specify a byte-size target.
initialQuality Choose starting encoder quality for lossy formats. Lower quality can reduce size and visible detail. It is not a universal size guarantee.
fileType Request an output format such as JPEG or WebP where supported. Format support varies; conversion can affect transparency and compatibility.
useWebWorker Request processing in a Web Worker. Worker support and deployment policy affect whether the worker path is available; the package documents fallback behavior.
onProgress Receive progress updates for UI feedback. Progress is processing feedback, not upload progress.
signal Pass an abort signal to cancel work. Wire it to component lifecycle or a cancel button and handle cancellation separately from errors.
preserveExif Choose whether to preserve EXIF metadata. Consider privacy, metadata needs, and orientation behavior explicitly.

Consult the package documentation for exact option semantics and supported values. For camera photos, verify orientation: EXIF orientation can affect display, and retaining metadata is a separate decision from applying orientation to pixels.

Handle file validation, cancellation, and previews

  • No selection: Return when files[0] is absent, as in the example.
  • Validate inputs: Use the input’s accept attribute for picker guidance, but validate file type and size on the server too. MIME types and file names alone do not establish that contents are safe.
  • Keep the UI responsive: Enable worker processing where supported. The library describes a main-thread fallback; its non-blocking worker path depends on browser support for OffscreenCanvas.
  • Cancel deliberately: Create an AbortController for a compression operation, pass its signal in the options, and abort when the user cancels or the operation is no longer needed. Treat AbortError as cancellation.
  • Preview safely: For a local preview, create an object URL with URL.createObjectURL(uploadFile), then call URL.revokeObjectURL(url) when the preview is replaced or removed.
  • Prevent stale uploads: If users can select multiple files rapidly, associate each operation with its own ID or cancel the prior one so an older result does not overwrite newer state.
  • Compare sizes: Check compressed.size against original.size. For already-small images or a conversion that grows the file, decide whether to upload the original.

Build a custom browser pipeline

A library is usually the simpler option. If you need to own the pipeline, browser APIs can decode an image into an ImageBitmap, draw it to a canvas, and encode a Blob with toBlob(). This basic example scales to a maximum dimension and encodes JPEG:

async function compressWithCanvas(file, maxDimension = 1920, quality = 0.82) {
  const bitmap = await createImageBitmap(file);
  const scale = Math.min(1, maxDimension / Math.max(bitmap.width, bitmap.height));
  const canvas = document.createElement('canvas');
  canvas.width = Math.max(1, Math.round(bitmap.width * scale));
  canvas.height = Math.max(1, Math.round(bitmap.height * scale));

  const context = canvas.getContext('2d');
  if (!context) {
    bitmap.close();
    throw new Error('Canvas 2D is unavailable');
  }
  context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
  bitmap.close();

  const blob = await new Promise((resolve, reject) => {
    canvas.toBlob((result) => {
      if (result) resolve(result);
      else reject(new Error('Image encoding failed'));
    }, 'image/jpeg', quality);
  });

  const base = file.name.replace(/\.[^.]+$/, '') || 'image';
  return new File([blob], `${base}.jpg`, { type: blob.type });
}

Example use: const smaller = await compressWithCanvas(file);, then append smaller to FormData as above. This example intentionally leaves format policy to you: JPEG does not preserve transparency. A custom pipeline also makes your application responsible for output naming and type, quality decisions, orientation, metadata, browser compatibility, cancellation, and error handling.

createImageBitmap() returns a Promise and supports decode options such as orientation and resizing; see MDN’s createImageBitmap documentation and Chrome’s API overview. Canvas dimensions are limited by browsers. The library documents that it adjusts dimensions to remain within canvas limits; test large files on the browsers and devices you support.

Or skip the browser setup

If the goal is to capture a webpage as an image rather than compress a user-selected upload, ScreenshotNeo returns a screenshot from one API request. See the API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed (${res.status})`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', bytes));

Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, and cache hits are not billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for free.

Troubleshooting

Symptom Likely cause What to do
The output is larger than the input. Small source file, encoding overhead, or unsuitable format conversion. Compare byte sizes and upload the smaller file. Test representative inputs and formats.
The image looks rotated or mirrored. EXIF orientation was interpreted differently during decode or metadata handling. Test camera photos, choose orientation behavior deliberately, and check the package’s orientation helpers and EXIF options.
Transparent areas become solid. The output was encoded as JPEG, which does not retain transparency. Choose a format and browser support policy that preserves transparency, and verify the actual output type.
WebP or another requested format is not produced. Browser canvas encoder support differs by browser. Check the returned Blob/File type and provide a supported fallback. Do not trust the requested type without checking output.
The page freezes or uses substantial memory. Large images require decoding and canvas memory; work may run on the main thread. Use worker processing where possible, cap dimensions, avoid retaining multiple decoded images, and test on lower-memory devices.
The worker fails under Content Security Policy. The deployed CSP may block the worker URL or blob: source used by the package. Review the package’s CSP guidance. Allow the documented source if appropriate or configure a self-hosted worker library URL.
Compression rejects or the canvas is blank. Unsupported/corrupt input, browser limits, a missing 2D context, or an encode failure. Catch the rejection, validate accepted types, check the context and returned Blob, and test large images on target browsers.
Selecting the same file does not trigger change. Some flows do not emit a new change event when the selected file is unchanged. Clear the input value after handling, as in the example.

Performance, reliability, and cost

Client-side compression can reduce the bytes sent over the network when the chosen output is smaller, but it adds local decode and encode work. Processing time and memory depend on image dimensions, browser, device, format, and settings; the available sources provide no independent benchmark or typical savings figure. Measure with representative images and devices before setting product claims or strict targets.

Worker processing can keep encoding work off the main thread when supported, while fallback behavior and canvas limits still matter. For reliability, preserve the original until compression succeeds, handle rejection and cancellation, verify the output’s size and type, and keep server-side validation. For cost, compare the CPU and memory work shifted to the user’s device against the upload and storage bytes saved; there is no universal break-even point.

For resizing-focused pipelines, pica documents worker, WebAssembly, createImageBitmap, and JavaScript fallback approaches. The sources do not establish a comparable speed or quality winner, so evaluate libraries with your own image set and supported browser matrix.

FAQ

Does React have a built-in image compressor?

No. React handles UI and state; image decoding and encoding come from browser APIs or a library.

Should I compress on the client or server?

Client-side processing can reduce upload bytes, while server processing gives the application control over a consistent pipeline. Choose based on device workload, upload constraints, and where the result must be trusted.

Does compression always reduce file size?

No. Compare the generated file’s byte size with the original and choose a fallback policy.

Will compression preserve EXIF?

That depends on the processing path and settings. Decide separately whether to preserve metadata and how to handle orientation.