Plate 55
mozjpeg vs libvips: 385ms vs 31ms Encode
mozjpeg cjpeg 3.3.1 (mozjpeg npm 8.0.0) vs libvips jpeg via sharp 0.35.5 (mozjpeg:false) on one 1920×1080 JPEG → q80. Warm medians (7 rounds after 1 warm-up): cjpeg 385 ms / 675,128 B vs libvips jpeg 31 ms / 675,714 B (~12×). Context: sharp mozjpeg:true 308 ms / 546,546 B. Fixture /workspace/bench-mozjpeg-libvips/. cjpeg timed with spawn + PPM read.
Aditya Challa4 min read
I encoded one 1920×1080 photo at JPEG quality 80 seven times on this box: mozjpeg cjpeg 3.3.1 median 385 ms (675 KB out), libvips jpeg via sharp 0.35 median 31 ms (676 KB out) — about 12× faster at nearly the same size.
Short answer: for in-process Node pipelines, libvips jpeg was the default here. Standalone mozjpeg still fits shell scripts and imagemin-style CLI hooks. No affiliate links in this post.
What I tested
- Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 00:26–00:28 IST.
- Versions: mozjpeg npm 8.0.0 shipping cjpeg 3.3.1 (build 20210529); sharp 0.35.5 with mozjpeg:false for the libvips jpeg path; Node 22.20.0.
- Fixture: /workspace/bench-mozjpeg-libvips/. Source src-1920.jpg is a 1920×1080 JPEG (~981 KB). Primary task: JPEG encode quality 80. cjpeg read a P6 PPM written from the same decode; sharp libvips path decoded the JPEG and encoded in-process. Context: sharp mozjpeg:true on the same JPEG.
- Timing: performance.now around each encode (cjpeg via execFileSync, so spawn is included); 1 warm-up each, then 7 interleaved rounds. Outputs written to out/ for size checks.
- Findings: mozjpeg cjpeg median 385 ms (370–476), out 675,128 B. libvips jpeg via sharp median 31 ms (29–32), out 675,714 B. sharp mozjpeg context median 308 ms / 546,546 B.
- One surprise: at the same nominal q80, sharp mozjpeg wrote ~129 KB less than either cjpeg or libvips jpeg on this photo, at a cost closer to cjpeg than to libvips.
- Not tested: vips CLI binary, png/webp/avif, trellis multipass flags beyond defaults, Windows or macOS, serverless cold starts.
How wide was the encode gap?
| Path | Median wall | Min / Max | Output JPEG |
|---|---|---|---|
| mozjpeg cjpeg 3.3.1 q80 (from PPM) | 385 ms | 370 / 476 | 675,128 B |
| libvips jpeg via sharp q80 | 31 ms | 29 / 32 | 675,714 B |
| sharp mozjpeg q80 (context) | 308 ms | 297 / 318 | 546,546 B |
Same source photo, same box. cjpeg times include process spawn and PPM read; libvips times are in-process. Even allowing for spawn, the gap stayed roughly an order of magnitude. For sharp vs gulp imagemin-mozjpeg, see Sharp vs imagemin. For sharp vs a JS codec on resize, see Sharp vs Jimp. For SVG optimize, see SVGO vs SVGR.
Does mozjpeg ever win this call?
Yes, when your pipeline is already a shell or imagemin plugin chain and you want mozjpeg-specific flags without taking a sharp native addon. On this Linux box, libvips jpeg returned in 31 ms median at almost the same bytes as cjpeg. If you need the smaller sharp mozjpeg output (~547 KB here), that is a different knob than plain libvips jpeg.
Treat this post as mozjpeg cjpeg vs libvips jpeg on one q80 encode, not a quality-panel shootout.
Should you switch JPEG encoders?
| Situation | My pick |
|---|---|
| Node CI or server with sharp already | libvips jpeg (or sharp mozjpeg if size wins) |
| Shell / Make / imagemin CLI only | mozjpeg cjpeg can stay |
| Need smallest q80 file in this lab | sharp mozjpeg |
| High-volume thumbnail farm | libvips / sharp |
| Watching asset budgets after images | Pair with size-limit vs bundlesize and SWC vs esbuild vs Babel |
| Cannot install native addons | Stay on cjpeg or WASM |
Bottom line: on one 1920 photo at q80, mozjpeg cjpeg returned in 385 ms median with a 675 KB file while libvips jpeg via sharp needed 31 ms and 676 KB. Who should not switch: teams whose only JPEG step is an existing cjpeg shell recipe and who cannot take sharp.
How this was made: I reused a 1920×1080 JPEG on the ShopperCove box, wrote a PPM for cjpeg, timed interleaved encodes against sharp libvips jpeg after a warm-up, recorded JSON medians and output bytes; the write-up was drafted with AI help and checked against that output.
Sources
- https://github.com/mozilla/mozjpeg
- https://www.npmjs.com/package/mozjpeg
- https://www.libvips.org/
- https://sharp.pixelplumbing.com/
- https://www.npmjs.com/package/sharp
Related
- https://www.shoppercove.com/blog/sharp-vs-imagemin
- https://www.shoppercove.com/blog/sharp-vs-jimp
- https://www.shoppercove.com/blog/svgo-vs-svgr
- https://www.shoppercove.com/blog/size-limit-vs-bundlesize
- https://www.shoppercove.com/blog/swc-vs-esbuild-babel
- https://www.shoppercove.com/blog/terser-vs-esbuild-swc-minify
- https://www.shoppercove.com/blog/vite-vs-parcel
- https://www.shoppercove.com/blog/source-map-explorer-vs-webpack-bundle-analyzer
Lab evidence
What I found running this
Hands-on on ShopperCove box 6 Oct 2026 ~00:26–00:28 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). mozjpeg npm 8.0.0 / cjpeg 3.3.1 vs sharp 0.35.5 libvips jpeg (mozjpeg:false). Fixture /workspace/bench-mozjpeg-libvips/: src-1920.jpg 1920×1080 JPEG ~981 KB; cjpeg from P6 PPM; sharp from JPEG. 1 warm-up + 7 interleaved rounds. Medians cjpeg 385 ms (370–476) / 675,128 B vs libvips jpeg 31 ms (29–32) / 675,714 B (~12×). Context sharp mozjpeg 308 ms / 546,546 B. Raw: /workspace/bench-mozjpeg-libvips/res-mozjpeg-libvips.json. Not tested: vips CLI, PNG/WebP/AVIF, trellis multipass, Windows/macOS, serverless cold starts. No affiliate.
Related links
Plate 26
Squoosh vs Sharp: 483ms vs 291ms Encode
@jsquash/jpeg 1.6.0 (Squoosh mozjpeg WASM) vs sharp 0.35.5 mozjpeg on one 1920×1080 JPEG → q80 encode from pre-decoded RGBA. Warm medians (7 rounds after 1 warm-up): Squoosh WASM 483 ms / 675,247 B vs sharp mozjpeg 291 ms / 546,546 B (~1.7×). Context: sharp decode+encode mozjpeg 303 ms; sharp libvips jpeg from raw 23 ms / 675,714 B. Fixture /workspace/bench-squoosh-sharp/. @squoosh/lib 0.5.3 fails on Node 22.
5 Oct 2026
Plate 95
Sharp vs imagemin: 305ms vs 399ms Encode
sharp 0.35.5 vs imagemin 9.0.1 + imagemin-mozjpeg 10.0.0 on one 1920×1080 JPEG → JPEG q80 (no resize). Warm medians (7 rounds after 1 warm-up): sharp 305 ms / 546,546 B vs imagemin-mozjpeg 399 ms / 675,128 B (~1.3×). Context: sharp resize 800 q80 median 115 ms / 142,435 B. Fixture /workspace/bench-sharp-imagemin/.
5 Oct 2026
Plate 88
Sharp vs Jimp: 112ms vs 366ms Resize
sharp 0.35.5 vs Jimp 1.6.1 on one 1920×1080 JPEG → width 800 JPEG q80. Warm medians (7 rounds after 1 warm-up): sharp 112 ms / 142,435 B vs Jimp 366 ms / 269,590 B (~3.3×). Fixture /workspace/bench-sharp-jimp/.
5 Oct 2026