How Shade Love Uses Small Pics
Shade Love sells custom window coverings: shades, blinds, drapery, woven woods, all configurable by size and material. Foster Commerce builds and maintains it on Craft Commerce, and it runs on Small Pics, an image CDN for Craft sites. This is what that looks like in a real codebase rather than in a getting-started guide.
The shape of the problem
A made-to-order product site is unusually image-heavy. Every fabric has a swatch. Every product has lifestyle photography. There is a configurator with measurement diagrams for a dozen mounting scenarios, an Instagram feed, carousels, and callout rows that pair imagery with colour.
Nobody uploads at the right size, because there is no single right size. The same fabric photograph appears as a small swatch in a grid and as a full-bleed hero.
What the templates actually call
Two functions cover almost everything. One generates a srcset, the other a single transform:
{% set imageSrcset = craft.smallpics.srcset(backgroundImage, {
'430w': { w: 430, h: 518 },
'550w': { w: 550, h: 662 },
'1160w': { w: 1160, h: 1396 },
'1440w': { w: 1440, h: 1733 }
}, { fit: 'cover', interlace: true }) %}
{% set imageSrc = craft.smallpics.transformImage(backgroundImage, {
w: 1440, h: 1733, fit: 'cover', interlace: true
}) %}
The srcset call takes breakpoints with their dimensions, then shared parameters applied to all of them. The transform call produces the single fallback src.
Note that both width and height are specified. On a site where layout depends on consistent aspect ratios, letting height float produces a grid that jumps around as images of different proportions load.
Three or four widths per component
Most components generate three widths, some four, occasionally two or six. Three rather than eight is a choice: every extra width is another variant to generate on first request, and the resolution difference between three well-chosen widths and eight is not something people notice.
Across the whole site those add up to 32 distinct widths, from 180 up to 1440. Each component asks for the numbers its own layout actually needs rather than rounding to a shared set, which is only practical because nothing is built ahead of time. With a build step, 32 widths would mean 32 renders of every image sitting in storage whether or not anyone ever requested them. Here a width nobody asks for is never generated.
There is no set of widths to register up front and no ceiling to design around. A component asks for what it needs and gets it. Each distinct width pays a one-off generation the first time someone requests it and is served from cache after that, so the cost of a wide spread of widths is a handful of slower first loads rather than anything ongoing.
The config file
The site config is short:
'transformNativeImages' => true,
'transformThumbnails' => true,
'thumbnailParams' => ['q' => 60],
'transformSvgs' => false,
'transformAnimatedGifs' => false,
'defaultParams' => ['q' => 75],
transformNativeImages does the heavy lifting. Craft has its own transform system, used throughout templates and by plugins. Turning this on routes all of that through Small Pics instead of the server, so existing template code keeps working untouched. There is no rewrite required before you see any benefit.
transformThumbnails extends that to the control panel, at a lower quality because a 200 pixel admin thumbnail does not need the same fidelity as a hero.
transformSvgs and transformAnimatedGifs are off. SVGs are already resolution independent, and animated GIFs do not survive the treatment usefully. Excluding both avoids paying for transforms that achieve nothing.
Where the quality setting changes
defaultParams sets q: 75 globally and thumbnails drop to 60. A handful of components go up to 80 or 84, where the subject is fabric texture and compression artefacts would misrepresent what the customer is buying.
Which is roughly how the compression trade-off plays out in practice: a default low enough to be invisible on most content, then extra bytes only where the content earns them.
Around 95 call sites
There are around 95 Small Pics calls across the templates. Heroes, carousels, card grids, swatch rows, callouts, the Instagram feed, configurator diagrams, product entries.
Optimizing the hero and leaving forty other images alone would not move much, because the bytes are in the long tail. Once the transform is a single Twig call, there is no reason not to use it everywhere.
If you are still weighing formats for a site like this, what is JPEG XL covers the newest option and where it stands. And for what a setup like this costs to run, image transform pricing compared works through the billing models.
Frequently asked questions
- Do I need Imager X to use Small Pics with Craft?
- No. Small Pics has a first-party Craft plugin with its own Twig API. Shade Love has Imager X installed for other reasons but calls Small Pics directly.
- Can Small Pics handle Craft's built-in transforms?
- Yes. Setting transformNativeImages to true routes Craft's native transforms through Small Pics, which means existing templates keep working untouched. transformThumbnails does the same for control panel thumbnails.
- How many image sizes should I generate per image?
- Shade Love generates three widths for most components and four for some, which covers the common breakpoints. Across the whole site that comes to 32 distinct widths. There is no set to register in advance and no limit to work around, so each component can ask for what its layout needs and a width nobody requests is never generated.
- What quality setting should I use?
- Shade Love defaults to q=75 site-wide, drops to 60 for control panel thumbnails, and raises to 84 on a handful of components where detail matters. Starting at 75 and adjusting where you can see a difference is a reasonable approach.
- Does Small Pics work with product configurators?
- Yes. Configurators request many variants of similar images, which is the case where generating on demand beats pre-building, because you cannot know in advance which combinations a visitor will look at.