spatie/statamic-responsive-images
Spatie's Responsive Images addon for Statamic CMS generates responsive image versions automatically, with a fieldtype for art direction and Glide presets driven by configurable breakpoints.
composer require spatie/statamic-responsive-images
php artisan vendor:publish --provider="Spatie\ResponsiveImages\ServiceProvider"
Requires Statamic 4.0+. Publishing drops config/responsive-images.php — that file is the first place to look, because almost every real decision (breakpoint names, WebP/AVIF defaults, quality per format, queue name, excluded containers) lives there rather than in the tag.
Second place to look: resources/views inside the package (publishable via the same vendor:publish prompt) — that's the actual <picture>/<img> markup you'll end up customizing on any design-heavy site.
The lowest-friction first use case is not the fieldtype. Take an existing assets field and wrap it:
{{ responsive:image_field alt="{title}" class="hero__image" }}
You immediately get a srcset, a sizes attribute, a base64 placeholder, and WebP variants — no template surgery, no Glide preset duplication. Only reach for the responsive fieldtype when an editor genuinely needs art direction (different crops or entirely different assets per breakpoint).
-
handle: image
field:
type: responsive
display: Image
container: assets
use_breakpoints: true
allow_ratio: true
allow_fit: true
breakpoints: [sm, md, lg]
Note use_breakpoints: true — without it, the breakpoint UI never appears and editors will think the field is broken.
Set the cache flag early, before you build anything. The recommendation is statamic.assets.image_manipulation.cache => false, so only browser-requested sizes are generated. Do this on day one, because flipping it later invalidates assumptions baked into your deploy scripts (see Gotchas).
Breakpoint-scoped parameters cascade, and that's the whole point.
{{ responsive:image_field ratio="1/1" lg:ratio="16/9" 2xl:ratio="2/1" }}
Default applies below lg, lg:… applies from lg up to 2xl, 2xl:… from 2xl up. Only ratio, src, quality, and glide: support breakpoint prefixes — anything else will land in the markup as an HTML attribute instead of erroring.
Cap the source dimensions. Glide parameters are passed through with a glide: prefix, and glide:width acts as a max width:
{{ responsive:image_field glide:width="1600" glide:blur="20" }}
This is the single best guard against a 6000px camera JPEG generating a dozen enormous derivatives. Pair it with a sensible container: assets upload validation rule at the field level.
Tune quality per format, per breakpoint.
{{ responsive:image_field quality:webp="50" md:quality:webp="75" }}
Global defaults go in config; tag overrides are for the hero image that can't look soft while the rest of the site stays lean.
Disable the placeholder where it costs more than it earns. The inline base64 blur adds weight to every response and is useless for below-the-fold or decorative images:
{{ responsive:image_field placeholder="false" }}
Keep it for hero/LCP images, drop it elsewhere.
Choose formats deliberately. JPG source → JPG + WebP by default. AVIF is available but is slow to encode; enable it globally only if you have queue workers and storage headroom, or opt in per tag:
{{ responsive:image_field webp="true" avif="true" }}
Move resize work off the request cycle. If you run Horizon or a queue, set the queue key in responsive-images.php. Also list import/temp/private containers under excluded_containers so bulk uploads don't spawn derivative jobs for assets nobody renders.
GraphQL/headless: the addon augments assets and the responsive fieldtype with a responsive field. Arguments mirror tag params with colons replaced by underscores — lg:glide:filter becomes lg_glide_filter. The fieldtype variant exposes a breakpoints key and accepts no arguments, so you cannot override ratio at query time for art-directed fields. Shape your content model with that in mind; use /cp/graphiql autocomplete rather than guessing.
Custom markup: publish the views and edit them rather than post-processing the output string. Same for a Blade-based front end — there is a responsive Blade directive in the package (not covered in the README, so verify the signature against the source before standardizing on it).
responsive:regenerate only works when image_manipulation.cache is true — i.e. exactly the setting the README tells you to turn off. On the recommended setup there is no bulk regeneration path; clear the Glide cache directory instead and let requests repopulate it. Plan the first deploy of a large media library accordingly, or pre-warm the popular URLs.APP_DEBUG=false. If placeholders vanish from markup, check storage/logs/laravel.log — not the browser.avif leaking into the <img> attribute list (v2.14.1), src saving as an array on art-directed fields (v2.14.1), fractional width/height causing cast warnings (v2.14.4). Anything below v2.14.4 on a modern Statamic install is avoidable pain.sizes is computed by JavaScript. That means the initial HTML has no accurate sizes, no-JS clients and some crawlers/preload scanners get the default, and static-exported HTML may pick oversized images. It also means the placeholder is doing real layout-stability work — don't disable it on LCP images.lg:ratios="16/9" isn't a recognized parameter, so it either gets ignored or leaks into the tag as an attribute. Always eyeball the rendered srcset and media strings after editing art direction, don't just trust the editor preview.breakpoints array (align with your CSS framework — Tailwind defaults ship out of the box), format/quality config keys, excluded_containers, queue. Between them you can cover nearly every real-world requirement without forking; fork only if you need a different srcset width algorithm.How can I help you explore Laravel packages today?