Rules
A rule is a matcher plus what to generate from the images it matches. The same rule objects go into the Vite plugin, the loader, the cli config and an import query, so this page applies to all of them.
Anatomy of a rule
Section titled “Anatomy of a rule”rules: [ // Animations: gif for the reach, webp for the size - both keep the frames. { match: '**/*.gif', format: ['gif', 'webp'] }, // Stickers and screenshots: png keeps the transparency. { match: '**/*.png', width: [1, 0.5], format: ['png', 'webp'] }, // Everything else. { width: [1920, 1280, 640], format: ['jpg', 'webp', 'avif'] }]| Field | Type | Default | What it does |
|---|---|---|---|
match | Matcher | Matcher[] | matches everything | Glob, media query or function to match the image by. |
fallthrough | boolean | false | Keep matching the rest of the rules after this one matched. |
width | number | number[] | source width | Widths to resize to. A value of 1 or less is a multiplier. |
format | ImageFormat | ImageFormat[] | source format | Formats to convert to: avif, webp, jpg, png, gif, svg. |
processing | ProcessingOptions | jpg.mozjpeg, png.palette | sharp output options per format. |
optimization | OptimizationOptions | none | Custom optimizer function per format. |
skipOptimization | boolean | false | Do not re-encode the original variant, and skip the optimizers. |
scalingUp | boolean | true | Emit variants requested wider than the source, capped at the source width. |
postfix | string | PostfixFormatter | @<width>w | Postfix added to the variant file name. |
match, fallthrough, width and format only exist on a rule. The encoder options below them - processing, optimization, skipOptimization, scalingUp and postfix - can also be set once on the integration itself, where they become the default for every rule, and a rule that sets the same option overrides it - processing and optimization merge with the integration-level ones per format instead of replacing them. concurrency and the disk cache are integration-level only.
Order and fallthrough
Section titled “Order and fallthrough”Rules are tried in order and matching stops at the first hit: an image is generated by exactly one rule, unless that rule sets fallthrough: true and lets the search continue.
rules: [ // Hero images get an extra large variant, and the general rule after it. { match: '**/hero.*', width: [2560], format: ['jpg', 'webp'], fallthrough: true }, { width: [1, 0.5], format: ['jpg', 'webp', 'avif'] }]Two rules that resolve to one and the same file produce it once - the first rule wins, as it does without fallthrough.
A rule without match matches everything, which makes it the catch-all, and a catch-all belongs last: rules after it are unreachable.
Matching
Section titled “Matching”match takes a glob, a media query, a function, or an array of them.
A picomatch pattern, tested against the absolute path of the source file with posix separators:
{ match: '**/*.{jpg,jpeg}'}Anchor patterns with **/, always. 'src/images/*.jpg' matches nothing, because the path it is tested against starts at the root of the file system; '**/src/images/*.jpg' is the pattern that works.
Media queries
Section titled “Media queries”A string with a width or height feature is read as a CSS media query and matched against the size of the source image, not against a viewport:
{ match: '(min-width: 1920px)', width: [1, 0.5, 0.25]}A comma separated list is an or, exactly as in css: '(min-width: 1920px), (min-height: 1920px)' matches an image that is large in either direction. Only width and height features turn a string into a media query - a parenthesised extglob is still a glob.
Functions
Section titled “Functions”{ // Panoramas only. match: (path, size) => size.width / size.height > 2, width: [1, 0.5], format: ['jpg', 'webp']}The matcher gets the source file path as the integration feeds it, the size in pixels as { width, height }, and the source file itself as { path, contents }.
Arrays are an and
Section titled “Arrays are an and”{ // Big jpgs, and nothing else. match: ['**/*.jpg', '(min-width: 1920px)']}Every matcher in the array has to match. For an or, write separate rules, one brace glob, or one comma separated media query list.
Widths
Section titled “Widths”width takes one number or a list of them:
- a value of 1 or less is a multiplier of the source width -
[1, 0.5]is “the original and a half sized copy”, and the multiplied width is rounded up; - a value above 1 is absolute pixels, and has to be an integer -
1.5fails the build withInvalid width: 1.5; - with no
widthat all only the source width is generated.
Pixels are never upscaled. A width above the source width still produces a variant, capped at the source width and named after the width it actually has: a 1600px source asked for 2400 yields photo@1600w.jpg. Set scalingUp: false to drop those variants instead of capping them - useful when the same rule set covers sources of very different sizes.
Requested widths that resolve to one and the same file are generated once, so an oversized list costs nothing beyond the encoding it does need.
The multiplier 1 is the one width that gets no postfix: it keeps the source file name, photo.jpg and photo.webp. An absolute width equal to the source width still gets one, photo@1600w.jpg.
Formats
Section titled “Formats”format takes one format or a list: avif, webp, jpg, png, gif, svg. jpeg and heif are accepted as aliases of jpg and avif; anything else fails the build with Unsupported image format: "tiff". With no format the source format is kept.
Put the widely supported format first and the modern ones after it - ['jpg', 'webp', 'avif']. The order only decides which variant is the fallback when the source format is not in the list, and that fallback is what a browser gets when it supports nothing else.
Png, gif and svg
Section titled “Png, gif and svg”Give these their own rules, before the catch-all:
- png converted to jpg loses its transparency. Keep png in the list:
format: ['png', 'webp']. - animated gif keeps its frames only as
giforwebp. Jpg and avif flatten it to a single frame. - svg is never resized and never converted. A rule passes it through only when its
formatis unset or includessvg, so a raster-onlyformatdrops it silently. The Vite plugin skips.svgimports entirely, and the loader should keep.svgout of itstest; the cli does pass svg files through.
Encoding options
Section titled “Encoding options”{ processing: { jpg: { quality: 80 }, avif: { effort: 6 } }, optimization: { // `optimize` is svgo's. svg: contents => Buffer.from(optimize(contents.toString()).data) }, postfix: (width, requestedWidth, format) => `-${width}`}processing- sharp output options per format, merged per format over the defaultsjpg: { mozjpeg: true }andpng: { palette: true }. Overridingjpg.qualitytherefore keepsmozjpegon. The defaults are chosen by SSIM and file size: mozjpeg gives around 30% smaller jpg files at the same quality,paletteturns on lossy quantization for png.optimization- a(contents, format) => Bufferfunction per format, applied to the already encoded bytes. It is the only way to touch svg, e.g. with svgo.skipOptimization- the variant that matches the source format at the source width is passed through byte for byte instead of being re-encoded, and the custom optimizers never run. Resized and converted variants are still encoded.postfix- a string, or a(width, requestedWidth, format) => stringformatter.widthis the actual output width,requestedWidthis what the rule asked for - the multiplier, when it was one. The formatter has to be pure: it is called repeatedly, including for variants that end up skipped, and its result is part of the cache key.
Where rules live
Section titled “Where rules live”| Where | How |
|---|---|
| Vite plugin | rules in the plugin options. |
| Loader | rules in the loader options. |
| Cli | rules in srcset.config.js. The -m, -w and -f arguments build one rule that replaces the whole list. |
| Import query | A JSON rule in the query replaces the whole rule set for that one import. |
Pitfalls
Section titled “Pitfalls”- No catch-all, no image. A rule set that misses an image leaves an empty module behind, and nothing on the page says so.
- An array in
matchis anand.['**/*.jpg', '**/hero*']matches only the jpgs whose name starts withhero. - Globs are absolute. Without a leading
**/a path pattern matches nothing. - The first format is the fallback only when the source format is missing from the list.
['avif', 'jpg']on a jpg source still hands out the jpg; on a png source it hands avif to everyone. - Png and gif in the catch-all. A general
format: ['jpg', 'webp', 'avif']strips transparency from png files and flattens animated gifs. They need rules of their own. - Svg in a raster rule. A
formatwithoutsvgdrops svg files without a word.