Responsive Images Config
config/responsive_images.ts is the single source of truth for responsive images. It defines the width breakpoints, encoder quality settings, and screenshot-specific behaviour. Two consumers read this file so they never drift — change a value here and both the generated image variants and the <responsive-image> markup update together.
| Consumer | Role |
|---|---|
scripts/prepare_images.ts | Generates width-stepped WebP + JPEG variants at build time |
lib/images.ts | srcset() and webp()/jpeg() helpers for <picture> markup |
Configuration File
export const responsive_widths = [300, 500, 800, 1440] as const;
export const responsive_quality = { webp: 80, jpeg: 80 } as const;
export const screenshot_quality = { jpeg: 85 } as const;
responsive_widths
export const responsive_widths = [300, 500, 800, 1440] as const;
Width breakpoints in pixels. For every image, the generator produces one variant at each width (plus a full-size recode). These same widths are fed to the srcset() helper to build the <source srcset="… 300w, … 500w, … 800w, … 1440w"> attributes that let the browser pick the best size.
Variants are never upscaled: a breakpoint larger than a given original's native width is clamped to that original's width. If an image is only 640px wide, the 800px and 1440px breakpoints are skipped for it — only the 300px, 500px, and full‑size variants are produced.
To adjust widths per project, edit the array here. CLI overrides (--widths 300,600,900,1200) override it at build time, but the srcset() helper always reads from this file, so a permanent width change should be made here to keep markup and files in sync.
responsive_quality
export const responsive_quality = { webp: 80, jpeg: 80 } as const;
Per-format encoder quality for photographic and WebP sources (JPEG, .jpg, .jpeg, .webp originals). Both values range from 1–100.
| Field | Description |
|---|---|
webp | Quality for WebP output — the primary format browsers download (~97% support). |
jpeg | Quality for JPEG output — the <img src> universal fallback. |
CLI overrides: --quality 75 sets both formats. --quality-webp 75 and --quality-jpeg 82 override individually. These flags only affect the generator (prepare_images.ts) — the srcset() helper uses the file's widths but not its quality.
PNG Sources — Screenshot Treatment
PNG originals receive different treatment because they're assumed to be screenshots: flat UI colour and sharp text that lossy DCT compression would smear.
When the source extension is .png and no --quality* CLI flag has been set:
- WebP is encoded losslessly. The
webpquality value is ignored —Bun.Image's{ lossless: true }option is used instead. This preserves pixel‑perfect text and UI elements. - JPEG uses
screenshot_quality.jpeg(85) instead ofresponsive_quality.jpeg(80). The<img src>fallback cannot be lossless, so it gets a higher‑quality JPEG to minimise smearing.
When a CLI quality flag is set (--quality, --quality-webp, or --quality-jpeg), the explicit override takes precedence and the PNG‑routing logic is bypassed — every source, including PNGs, uses the user‑supplied quality values.
screenshot_quality
export const screenshot_quality = { jpeg: 85 } as const;
Quality override applied to the JPEG fallback of .png sources (see PNG Sources — Screenshot Treatment above). Only jpeg is defined here because the WebP side of a PNG source is handled as lossless, not quality‑driven.
| Field | Default | Description |
|---|---|---|
jpeg | 85 | JPEG quality for the <img src> fallback of PNGs. |
Output Layout
The generator writes variants into src/public/images/responsive/ with this structure:
src/public/images/responsive/
hero.png ← full‑size recode, JPEG bytes (source extension kept)
hero.webp ← full‑size recode, WebP
300/
hero.png ← 300px‑wide variant, JPEG bytes
hero.webp ← 300px‑wide variant, WebP
500/
hero.png
hero.webp
800/
hero.png
hero.webp
1440/
hero.png
hero.webp
Sub‑folders under assets/images/ are preserved in the output. The width sub‑directories are named after the breakpoints in this config — changing responsive_widths changes the directory names and the srcset descriptor values together.
JPEG Extension Quirk
JPEG fallback files keep the source extension: hero.png contains JPEG bytes, and hero.jpg stays as hero.jpg. Only .webp sources get a .jpg JPEG fallback. This preserves backwards compatibility with the webp()/jpeg() URL helpers and the <responsive-image> component's <img src> attribute, which points at the source‑extension path.
Dual‑Consumer Design
The shared config eliminates the drift that occurs when a width list is defined separately in the generator and the template layer:
config/responsive_images.ts ← single source of truth
│
├── scripts/prepare_images.ts ← reads widths + quality at build time
│
└── lib/images.ts ← reads widths at render time for srcset
│
└── <responsive-image> ← renders <picture> with correct candidates
Change responsive_widths in one place, re‑run bun prepare:images, and the component automatically references the new breakpoints. No manual sync required.
CLI Overrides
The generator (bun prepare:images) accepts flags that temporarily override the config values without editing the file. These are useful for one‑off builds or experimenting with quality settings.
| Flag | Default (from config) | Description |
|---|---|---|
--widths <list> | responsive_widths | Comma‑separated breakpoints, e.g. 300,600,900 |
--quality <n> | responsive_quality | Sets both WebP and JPEG quality |
--quality-webp <n> | responsive_quality.webp | Override WebP only |
--quality-jpeg <n> | responsive_quality.jpeg | Override JPEG only |
--force | off | Re‑encode even when outputs are up‑to‑date |
When a --quality* flag is used, the PNG screenshot routing is bypassed and all sources use the explicit values — this is how the generator distinguishes "user explicitly set quality" from "no flag, use PNG‑aware defaults."