跪拜 Guibai
← Back to the summary

Cutting a content site's image payload by 70%: browser-side compression wins

Last month I got a task: optimize the loading speed of the company's content site. Lighthouse ran, Performance score 38, the largest banner image was 2.4MB, first screen took 6+ seconds. The boss said "go fix it."

Honestly I've done this kind of work before, but each time it was rough — either have design re-export smaller images, or just hang an ImageMagick compression on the server. This time it's different: the site has several hundred articles, each with several images, and manually processing all of them isn't realistic.

So I needed a solution that could run in batch, or better, automatically compress images before the user uploads them.

First, figure out where the problem is

Looking through Chrome DevTools Network panel, the problems concentrated on three types of images:

  1. Article images uploaded by ops, many taken directly from phone photos, 4000x3000 pixel JPEGs, 3-5MB each
  2. Product screenshots, PNG format, a single 1920x1080 screenshot can be 800KB to 1.5MB
  3. Cover images of some old articles, mixed formats, JPG, PNG, WebP all present

Actually the core problem is one thing: images are too large, and they go straight to the CDN without any compression.

Solution selection

I looked into several common approaches:

Option 1: Server-side compression

After the user uploads, compress on the server with Sharp (Node.js) or ImageMagick. This is the most traditional approach; the advantage is that processing logic is centralized and easy to control.

The downside is obvious: it consumes server resources. Our small site runs on a 2C4G machine; the Node service was already tight, and adding image processing spiked CPU to 90% at peak. Also, uploading large images itself takes time — a user uploading a 5MB image takes several seconds just to upload, then compression starts.

Option 2: CDN image processing

Alibaba Cloud OSS and Qiniu both support image processing parameters, appending things like ?x-oss-process=image/resize,w_800/quality,q_80 to the URL. It's convenient, no need to write your own code.

But there's a catch: CDN processing is billed per request, and the caching strategy for processed images needs extra configuration. More importantly, some of our images are embedded in rich text as img tags with no parameters, so replacing them all would be tedious.

Option 3: Browser-side compression

Compress the image in the browser before the user uploads it. Using the Canvas API or a WebAssembly decoder, compression can be done on the client, and the file uploaded to the server is already the compressed small file.

This option appealed to me the most — it doesn't consume server resources, upload speed improves (because the file is smaller), and it solves the problem at the source.

What I actually did

In practice I used two things in combination:

For the backend upload scenario, I added a frontend compression step in the editor. After the user selects the image, I first compress with the browser-image-compression library in the browser, then upload. The core config looks roughly like this:

import imageCompression from 'browser-image-compression';

async function compressBeforeUpload(file) {
  const options = {
    maxSizeMB: 0.5,
    maxWidthOrHeight: 1920,
    useWebWorker: true,
  };
  return await imageCompression(file, options);
}

This library uses Canvas redraw under the hood, handles both JPEG and PNG, with a compression ratio of about 60%-80%. A 3MB phone photo compresses to roughly 300-500KB, which is plenty for website display.

For existing images and the occasional image needing manual handling, I used an online tool called Glazlo. It's purely browser-side — images are never uploaded to any server, everything happens locally. I chose it because some images are product screenshots containing internal interface information, and uploading to a third-party service isn't appropriate. Glazlo's processing logic all runs in the browser; I confirmed via F12 Network panel that no external requests are made.

It supports specifying a target file size — for example, you can set "compress to under 200KB" and it automatically adjusts quality parameters to approach that target. This feature is especially useful for WeChat official account cover images — the platform requires covers under 2MB, but compressing to 200KB actually gives a much better loading experience.

Results

After finishing, I reran Lighthouse:

Honestly, the results are better than I expected. The 70% reduction in volume mainly came from JPEG images — phone photos have huge compression headroom. PNG screenshots have less headroom, with roughly a 40% reduction.

Several pitfalls I hit

1. Canvas compressing PNG loses transparency

canvas.toBlob() outputting JPEG drops the alpha channel, and transparent areas become black. If the original image has transparency, either keep PNG format or manually fill in background color when converting to JPEG.

2. WebP compatibility

Safari below 14 doesn't support WebP. If a large portion of your user base still has iOS 13 devices (we measured about 5%), don't force WebP site-wide. Do a format fallback: serve WebP if supported, fall back to JPEG otherwise.

3. Quality parameter isn't always better when lower

Set JPEG quality below 50, and text and line edges show obvious color banding. For images with text like product screenshots, quality should not go below 75. Photos can go to 60 or even lower with little visible difference.

4. Don't forget an image can have multiple sizes

With responsive images using <picture> or srcset, don't forget to compress every size. I only compressed the original at first, but the 400px-wide thumbnail was still generated on-the-fly by the CDN, wasting a round trip.

Summary

Image compression is both complex and simple. The key is to find a solution that fits your project scenario:

Don't chase extreme compression ratios. Getting a 3MB image down to 300KB already wins 90% of loading performance. Leave the remaining 10% for later optimization.

Comments

Top 2 from juejin.cn, machine-translated. The original thread is authoritative.

汪汪队首席上单

How do I analyze it? I don't know how to use F12.

lollollollol

Learned it [👍]