Why build-time image tools are not enough
Plugins that generate responsive images during your build are excellent for images that live in your repository. They cannot help with images that users upload, or that editors add through a CMS, because those do not exist when you build.
For those you need the variants to be created when requested. If your images are already behind a URL that can resize, a srcset is just several URLs.
The idea: one srcset, several widths
srcset lists the same image at different widths. The browser picks one based on the display size and pixel density. sizes tells it how wide the image will be on the page, so it can choose before layout. Fileboy produces each width from the stored original.
<img
src="https://api.fileboy.io/images/get/FILE_ID?transform=resize:800x_"
srcset="
https://api.fileboy.io/images/get/FILE_ID?transform=resize:480x_ 480w,
https://api.fileboy.io/images/get/FILE_ID?transform=resize:800x_ 800w,
https://api.fileboy.io/images/get/FILE_ID?transform=resize:1200x_ 1200w,
https://api.fileboy.io/images/get/FILE_ID?transform=resize:1600x_ 1600w
"
sizes="(min-width: 960px) 800px, 100vw"
width="800"
height="533"
loading="lazy"
alt="A description of the image"
/>Always set width and height (or an aspect ratio in CSS). It lets the browser reserve space before the image loads, which stops the page jumping.
Generate it from code
In an app you will not write those URLs by hand. A small component builds the srcset from the file ID, using the same client you use elsewhere.
import { useFileboy } from "@cd2/fileboy-react"
const WIDTHS = [480, 800, 1200, 1600]
interface IProps {
id: string
alt: string
sizes: string
width: number
height: number
}
export function ResponsiveImage({ id, alt, sizes, width, height }: IProps) {
const { buildImageUrl } = useFileboy()
const urlAt = (w: number, format?: "webp") =>
buildImageUrl(id, {
transforms: [
{ type: "resize", width: w },
...(format ? [{ type: "format" as const, format }, { type: "quality" as const, value: 80 }] : []),
],
})
const srcSet = (format?: "webp") => WIDTHS.map(w => `${urlAt(w, format)} ${w}w`).join(", ")
return (
<picture>
<source type="image/webp" srcSet={srcSet("webp")} sizes={sizes} />
<img
src={urlAt(width)}
srcSet={srcSet()}
sizes={sizes}
width={width}
height={height}
loading="lazy"
alt={alt}
/>
</picture>
)
}The <picture> element offers WebP explicitly: browsers that support it take the WebP source and the rest fall back to the plain img. You can also leave the choice to Fileboy: with format:auto in every URL, it reads the browser's Accept header and serves AVIF, then WebP, so a single img with a srcset is enough.
Choosing widths
- Start from the widths your layout really renders, doubled for high-density screens, rather than a long generic list.
- Four to six widths is plenty. More adds little and every extra width is another file to generate and cache.
- Use the same set everywhere. Reusing widths across pages means each variant is created once.
- For fixed-shape thumbnails, use a
resizewith both dimensions andfit:coverinstead of width-only, so every card has the same proportions.
Checked against the current libraries in November 2026. Found something that no longer works? Tell us.