Prompt
I'm building a static site and want image optimization in the build step
Latest observation
Nov 3, 2026
OpenAI APIWeb search: off
A good static-site image optimization build step usually means:
- Resize images to the sizes you actually serve
- Convert to modern formats like WebP/AVIF
- Compress aggressively but with acceptable quality
- Generate responsive variants so the browser can pick the best one
- Cache results so builds stay fast
A practical setup
If you use a JS/TS build tool
Use a library like sharp to process images during build.
Common approach:
- Source images in
src/images - Build script outputs optimized files to
dist/images - Generate multiple widths, e.g.
320, 640, 960, 1280 - Emit HTML with
srcsetandsizes
Example pipeline:
- input:
photo.jpg - output:
photo-320.avifphoto-640.avifphoto-960.avifphoto-1280.avif- same for WebP and maybe JPEG fallback
If you use a static site generator
Many have built-in or plugin-based optimization:
- Astro: built-in image integrations
- Next.js export: use
next/imagecarefully; for pure static export you may need manual preprocessing - Eleventy: plugins or custom scripts
- Hugo/Jekyll: usually custom build scripts or external tools
Recommended output strategy
For each image:
- keep original if needed
- create:
- AVIF for modern browsers
- WebP as a fallback
- JPEG/PNG fallback only if necessary
- create multiple widths
- preserve aspect ratio
- strip metadata unless you need it
Simple Node.js example with sharp
import fs from 'fs/promises';
import path from 'path';
import sharp from 'sharp';
const inputDir = 'src/images';
const outputDir = 'dist/images';
const widths = [320, 640, 960, 1280];
const formats = ['avif', 'webp', 'jpeg'];
await fs.mkdir(outputDir, { recursive: true });
const files = await fs.readdir(inputDir);
for (const file of files) {
const inputPath = path.join(inputDir, file);
const name = path.parse(file).name;
for (const width of widths) {
for (const format of formats) {
const outPath = path.join(outputDir, `${name}-${width}.${format}`);
await sharp(inputPath)
.resize({ width, withoutEnlargement: true })
.toFormat(format, {
quality: format === 'jpeg' ? 80 : 60
})
.toFile(outPath);
}
}
}
HTML usage
<img
src="/images/photo-640.jpeg"
srcset="
/images/photo-320.avif 320w,
/images/photo-640.avif 640w,
/images/photo-960.avif 960w,
/images/photo-1280.avif 1280w
"
sizes="(max-width: 768px) 100vw, 768px"
alt="Description"
/>
Better is to use <picture> for format fallback:
<picture>
<source
type="image/avif"
srcset="/images/photo-320.avif 320w, /images/photo-640.avif 640w, /images/photo-960.avif 960w, /images/photo-1280.avif 1280w"
sizes="(max-width: 768px) 100vw, 768px"
/>
<source
type="image/webp"
srcset="/images/photo-320.webp 320w, /images/photo-640.webp 640w, /images/photo-960.webp 960w, /images/photo-1280.webp 1280w"
sizes="(max-width: 768px) 100vw, 768px"
/>
<img src="/images/photo-640.jpeg" alt="Description" />
</picture>
Tips
- Use a hash in filenames for cache busting
- Don’t generate too many sizes; 4–6 is often enough
- Strip EXIF unless you need orientation/location data
- Consider placeholders (LQIP/blur-up) for better perceived performance
- Use SVGs as-is when possible; they usually don’t need optimization
Best tools
- sharp: fast and flexible for build scripts
- imagemin: older but still usable
- Squoosh CLI: good for experimentation
- ffmpeg: for video/poster frames, not images
If you want, I can give you:
- a drop-in Node build script
- an Eleventy/Astro/Vite plugin setup
- or a GitHub Actions build step for image optimization.