# Install (macOS)
-weight: 500;">brew -weight: 500;">install mozjpeg # Install (Linux — build from source)
-weight: 500;">git clone --branch v4.1.5 https://github.com/nickt/mozjpeg.-weight: 500;">git
cd mozjpeg && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. && make && -weight: 600;">sudo make -weight: 500;">install # Compress at quality 80
cjpeg -quality 80 -outfile output.jpg input.jpg # Batch compress all JPEGs in a directory
for f in *.jpg; do cjpeg -quality 80 -outfile "compressed_${f}" "$f"
done
# Install (macOS)
-weight: 500;">brew -weight: 500;">install mozjpeg # Install (Linux — build from source)
-weight: 500;">git clone --branch v4.1.5 https://github.com/nickt/mozjpeg.-weight: 500;">git
cd mozjpeg && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. && make && -weight: 600;">sudo make -weight: 500;">install # Compress at quality 80
cjpeg -quality 80 -outfile output.jpg input.jpg # Batch compress all JPEGs in a directory
for f in *.jpg; do cjpeg -quality 80 -outfile "compressed_${f}" "$f"
done
# Install (macOS)
-weight: 500;">brew -weight: 500;">install mozjpeg # Install (Linux — build from source)
-weight: 500;">git clone --branch v4.1.5 https://github.com/nickt/mozjpeg.-weight: 500;">git
cd mozjpeg && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. && make && -weight: 600;">sudo make -weight: 500;">install # Compress at quality 80
cjpeg -quality 80 -outfile output.jpg input.jpg # Batch compress all JPEGs in a directory
for f in *.jpg; do cjpeg -quality 80 -outfile "compressed_${f}" "$f"
done
# Strip metadata and optimize
jpegtran -optimize -copy none -outfile output.jpg input.jpg # Convert to progressive JPEG
jpegtran -optimize -progressive -copy none -outfile output.jpg input.jpg
# Strip metadata and optimize
jpegtran -optimize -copy none -outfile output.jpg input.jpg # Convert to progressive JPEG
jpegtran -optimize -progressive -copy none -outfile output.jpg input.jpg
# Strip metadata and optimize
jpegtran -optimize -copy none -outfile output.jpg input.jpg # Convert to progressive JPEG
jpegtran -optimize -progressive -copy none -outfile output.jpg input.jpg
# Compress a single file
magick convert -quality 80 input.jpg output.jpg # Batch convert a folder
magick mogrify -quality 80 -path ./compressed/ *.jpg # With chroma subsampling for additional savings on photos
magick convert -quality 80 -sampling-factor 4:2:0 input.jpg output.jpg
# Compress a single file
magick convert -quality 80 input.jpg output.jpg # Batch convert a folder
magick mogrify -quality 80 -path ./compressed/ *.jpg # With chroma subsampling for additional savings on photos
magick convert -quality 80 -sampling-factor 4:2:0 input.jpg output.jpg
# Compress a single file
magick convert -quality 80 input.jpg output.jpg # Batch convert a folder
magick mogrify -quality 80 -path ./compressed/ *.jpg # With chroma subsampling for additional savings on photos
magick convert -quality 80 -sampling-factor 4:2:0 input.jpg output.jpg
-weight: 500;">npm -weight: 500;">install [email protected]
-weight: 500;">npm -weight: 500;">install [email protected]
-weight: 500;">npm -weight: 500;">install [email protected]
import sharp from 'sharp'; await sharp('input.jpg') .jpeg({ quality: 80, mozjpeg: true }) .toFile('output.jpg');
import sharp from 'sharp'; await sharp('input.jpg') .jpeg({ quality: 80, mozjpeg: true }) .toFile('output.jpg');
import sharp from 'sharp'; await sharp('input.jpg') .jpeg({ quality: 80, mozjpeg: true }) .toFile('output.jpg');
import sharp from 'sharp';
import { readdir } from 'fs/promises';
import path from 'path'; const inputDir = './images';
const outputDir = './compressed';
const files = await readdir(inputDir); await Promise.all( files .filter(f => f.match(/\.(jpg|jpeg)$/i)) .map(file => sharp(path.join(inputDir, file)) .jpeg({ quality: 80, mozjpeg: true }) .toFile(path.join(outputDir, file)) )
);
import sharp from 'sharp';
import { readdir } from 'fs/promises';
import path from 'path'; const inputDir = './images';
const outputDir = './compressed';
const files = await readdir(inputDir); await Promise.all( files .filter(f => f.match(/\.(jpg|jpeg)$/i)) .map(file => sharp(path.join(inputDir, file)) .jpeg({ quality: 80, mozjpeg: true }) .toFile(path.join(outputDir, file)) )
);
import sharp from 'sharp';
import { readdir } from 'fs/promises';
import path from 'path'; const inputDir = './images';
const outputDir = './compressed';
const files = await readdir(inputDir); await Promise.all( files .filter(f => f.match(/\.(jpg|jpeg)$/i)) .map(file => sharp(path.join(inputDir, file)) .jpeg({ quality: 80, mozjpeg: true }) .toFile(path.join(outputDir, file)) )
);
jpegtran -progressive -optimize -copy none -outfile output.jpg input.jpg
jpegtran -progressive -optimize -copy none -outfile output.jpg input.jpg
jpegtran -progressive -optimize -copy none -outfile output.jpg input.jpg
await sharp('input.jpg') .jpeg({ quality: 80, progressive: true }) .toFile('output.jpg');
await sharp('input.jpg') .jpeg({ quality: 80, progressive: true }) .toFile('output.jpg');
await sharp('input.jpg') .jpeg({ quality: 80, progressive: true }) .toFile('output.jpg'); - Splits the image into 8x8 pixel blocks
- Transforms each block into frequency data (low frequencies = broad color areas, high frequencies = sharp edges and fine detail)
- Applies a quantization table that rounds down the high-frequency data based on your quality setting
- Encodes the result with Huffman compression - Drop your JPEG (or up to 20 JPEGs for batch mode)
- Adjust the quality slider — the live preview updates in real time
- Check the file size reduction counter - Large images on slow connections (users see something immediately)
- Above-the-fold hero images (perceived performance improvement)
- Any image where users might -weight: 500;">start interacting before it fully loads - You need maximum file size reduction with no additional quality loss
- You are serving images programmatically and can control the format
- Your build pipeline already handles format conversion - You are delivering to legacy systems or contexts that require JPEG specifically
- The receiving end does not support WebP (email clients, some CMSes, older apps)
- You need universal compatibility without format detection logic