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:
- Article images uploaded by ops, many taken directly from phone photos, 4000x3000 pixel JPEGs, 3-5MB each
- Product screenshots, PNG format, a single 1920x1080 screenshot can be 800KB to 1.5MB
- 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:
- Performance score went from 38 to 72
- Largest Contentful Paint dropped from 6.2s to 2.1s
- Total homepage image size dropped from 4.8MB to 1.4MB
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:
- If image volume is large and uploads are frequent, prioritize frontend compression to lighten the load at the source
- If the team has ops resources, server-side compression plus CDN is also stable
- If you only occasionally need to handle a few images, a reliable online tool is enough
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.
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.
Learned it [👍]