mia/statamic-image-renderer
A Statamic addon for rendering images through configurable presets, with responsive srcsets, optimized markup, and simple Antlers/Blade usage for flexible, cache-friendly image output.
Install the addon and pull in its config in one pass:
composer require mia/statamic-image-renderer
php artisan vendor:publish --tag=statamic-image-renderer-config
Then look first at the published config file (typically config/responsive-image.php or config/statamic/responsive-image.php). That file defines the breakpoints, generated widths, output formats (webp/avif/jpg), the blurhash/placeholder behaviour, and the quality/fallback settings that drive every tag call. The second place to check is the addon's resources/views directory: the package ships its own Blade/Antlers template that renders the <picture>/<img> markup, and this is the file you will most often copy into resources/views/vendor/ to customise.
The fastest first use case is replacing a plain Glide-tagged image in an existing template. Where you currently have something like:
<img src="{{ image:url width="800" }}" alt="{{ alt }}">
swap in the addon's responsive tag:
{{ responsive_image :src="image" :alt="alt" }}
This produces a full <picture> element with a srcset derived from the configured breakpoints, a sizes attribute, and modern-format <source> elements. Commit one working example before touching config — defaults exist for a reason and confirm the addon is wired up.
If you prefer Blade, use the component form inside your views:
<x-responsive-image :src="$entry->image" :alt="$entry->alt" class="w-full" />
Asset field → tag. The most common integration passes a Statamic Assets field value directly into the tag. The tag handles the Asset object, resolves URLs through Glide, and generates the responsive set. Keep the alt attribute as an explicit parameter rather than relying on the field's metadata, so editors control accessibility copy independently of the image choice.
Per-component breakpoint overrides. Rather than editing the global config for a single hero, pass widths inline. This keeps the global config as the default and lets a card component request a narrower set:
{{ responsive_image :src="image" :widths="[400, 800, 1200]" sizes="(min-width: 1024px) 33vw, 100vw" }}
The sizes string is where most layouts fall down — it must describe the CSS layout, not the image's intrinsic size. Treat it as part of the component's styling, and mirror it whenever the component's grid changes.
Bard / Replicator sets. Inside a Replicator loop, the addon's tag works naturally with the set's asset field. For sets that need distinct sizing (a full-bleed image versus an inline card), drive the parameters off a select field on the set so editors can pick a "layout" without touching code, then map that to widths/sizes via an Antlers array or a small view partial.
Blade component composition. For Laravel-first teams, wrapping the package component in your own resources/views/components/app-image.blade.php is a clean seam. Your wrapper owns the project's sizes conventions, lazy-loading defaults, and alt fallbacks; the package only handles the responsive rendering. This means swapping the renderer later touches one file.
Low-quality placeholders (blurhash). The addon can emit a blurhash-derived placeholder for an image, which is what makes it worth using over a hand-rolled srcset for media-heavy pages. The blurhash is computed from the asset file, so it only works when the tag receives a real Asset (a local, readable path) rather than a bare URL string. Enable it in config, check the rendered placeholder attribute/inline style in the markup, and cache the resulting hash per asset rather than regenerating on every render.
Modern-format preference. Configure webp/avif in the config and let the package emit <source> elements ahead of the <img> fallback. Verify on a real Glide disk that the derivative files land in the expected path — the addon caches generated variants through Glide, so a misconfigured disk shows up as broken sources rather than an error.
Layout-aware defaults. Set a sensible global sizes and width ladder once, then only override in the handful of components that truly deviate (hero, thumbnail, full-bleed). This keeps the number of distinct generated variants — and thus Glide cache size — under control.
Config caching masks changes. After editing the addon config, run php artisan config:clear (and config:cache in production). A stale cached config is the most common reason a new breakpoint "doesn't work".
Blurhash needs a path, not content (fixed in 1.0.23). Releases before 1.0.23 passed the file contents into the blurhash encoder instead of the file path, which produced broken, empty, or wildly wrong placeholders. If you enabled placeholders on an earlier release and saw garbage or missing hashes, upgrade to 1.0.23 and clear any persisted hashes (asset cache/Glide cache) — the bad values will otherwise survive the upgrade and keep rendering.
Glide cache growth. Each distinct width/format/quality combination is a separate generated file. Broad one-off widths overrides scattered across templates multiply the cache. Audit storage/app/glide (or your configured cache path) when disk usage creeps — the fix is usually consolidating width sets, not clearing blindly.
sizes versus widths mismatch. If sizes claims a smaller viewport than the widths provide for, browsers download oversized images silently. If it claims larger, you get soft images. Test with the browser's device toolbar and inspect the chosen candidate in the network panel, not just the network tab's byte count.
Missing LCP priority. The package renders standard lazy-loading defaults; the hero image above the fold should be the exception. Explicitly set loading/eager and fetchpriority="high" on the LCP image, otherwise the generated <picture> will lazy-load the most important image on the page. If you are using a blurhash placeholder on that hero, keep the placeholder lightweight — it should not compete with the LCP candidate.
Publishing views. The package's shipped template is a starting point, not a finished component. Publish it into resources/views/vendor/ before customising; editing inside vendor/ is lost on update. The template is also the extension point for adding class, data-*, blurhash placeholder markup, or wrapping markup.
Non-Asset sources. Passing a raw URL string instead of an Asset object usually works for the responsive set but bypasses Statamic's asset metadata (dimensions, focal point) and disables blurhash generation entirely. If focal-point cropping, the asset's own alt, or placeholders matter, pass the field value, not image:url.
Queue and memory during build. On sites with many entries, generating variants (and computing blurhashes) synchronously during a page render can spike response time on first hit. Warm the cache as part of deploy (crawl the site, or use a warmup command) rather than letting the first visitor pay for it.
Version pinning. The package is small and lightly starred; the tag name and config keys can shift between minor releases. Pin the version in composer.json — ^1.0.23 is a reasonable floor now that the blurhash path bug is fixed — and read the changelog before bumping, since this addon has no large dependent base to surface breaking changes publicly.
Debugging output. When markup looks wrong, dump the resolved parameters before the tag renders — the config values feeding widths and formats are the usual suspects, closely followed by a src that resolved to null because the field was empty, or an asset whose file is missing on disk (which silently kills blurhash generation). Guard with {{ if image }} around the tag in list views.
How can I help you explore Laravel packages today?