Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ux Lazy Image Laravel Package

symfony/ux-lazy-image

Symfony UX LazyImage makes lazy-loading images simple in Symfony apps. It offers a Twig component for native browser lazy loading, optional placeholders, and performance-friendly rendering that defers offscreen images, reduces initial page weight, and helps improve Core Web Vitals.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package

    composer require symfony/ux-lazy-image
    

    Requires a recent Symfony (7.1+). It is a Symfony UX split-repo package — developed in the symfony/ux monorepo and auto-split here, so it is officially maintained even though the repo shows low star count.

  2. Wire up the JavaScript (once per project)

    • AssetMapper: php bin/console importmap:require @symfony/ux-lazy-image
    • Webpack Encore: npm install @symfony/ux-lazy-image, then import it in assets/app.js or let controllers.json auto-register the lazy-image Stimulus controller.
    • Confirm the controller is registered in the browser (window.Stimulus), otherwise images will render as unstyled placeholders or not load.
  3. Look at these files first

    • vendor/symfony/ux-lazy-image/templates/components/LazyImage.html.twig (the markup contract)
    • src/Twig/LazyImageExtension.php (the ux_lazy_image() function)
    • assets/src/lazy_image_controller.js (the loading / blurhash logic)
  4. First use case — replace heavy <img> tags in a list or gallery:

    <twig:ux:lazy-image
        src="{{ asset('images/hero.jpg') }}"
        alt="Hero"
        width="1200"
        height="630"
    />
    

Implementation Patterns

Basic component usage

<twig:ux:lazy-image
    src="/images/photo.jpg"
    alt="A photo"
    width="600"
    height="400"
    class="rounded shadow"
/>

Any extra HTML attribute (class, id, style, loading, fetchpriority, decoding, data-*) is passed through to the rendered <img> — use this for CSP nonces or analytics hooks.

Twig function form (for dynamic attribute arrays)

{{ ux_lazy_image('/images/' ~ image.filename, image.alt, {
    width: image.width,
    height: image.height,
    class: 'card-img-top',
    srcset: '/images/small.jpg 320w, /images/large.jpg 1024w',
    sizes: '(max-width: 640px) 100vw, 640px',
}) }}

Use this when you build attributes programmatically (e.g. inside an entity loop or a macro).

Blurhash placeholders

Generate the hash once (via blurha.sh or the kornrunner/blurhash PHP library), store it on the entity/asset, and pass it in:

<twig:ux:lazy-image
    src="{{ asset('uploads/' ~ image.path) }}"
    alt="{{ image.alt }}"
    blurhash="{{ image.blurhash }}"
    width="{{ image.width }}"
    height="{{ image.height }}"
/>

The Stimulus controller paints the canvas placeholder and then swaps in the real image once it intersects the viewport. Because hashes are precomputed and stored, no per-request compute happens server-side — a good pattern is a blurhash column populated in an upload listener or a console command.

Responsive galleries / cards

The typical workflow is a Twig loop where size and srcset come from your image variant service (LiipImagineBundle, Glide, or a custom URL builder). Combine with width/height from the database so the browser reserves layout space and CLS stays at zero.

Template & controller overrides

  • Override templates/components/LazyImage.html.twig in your app to change wrapper markup (e.g. add a <picture> element).
  • Add a custom controller by registering your own data-controller value, or extend the shipped one via a Stimulus subclass if you need extra behavior (custom placeholder blur, fade-in animation).
  • Decorate Symfony\UX\LazyImage\Twig\LazyImageExtension if you need to inject default attributes (e.g. always add decoding="async").

Integration with AssetMapper / CDNs

Always use asset() or importmap-managed URLs for the src; hardcoded paths break under versions/CDN base paths. For CDN-served images, pass the full CDN URL from config rather than composing it in Twig.

Gotchas and Tips

  • JS is required. The <img> is populated by the Stimulus controller; if the controller is not registered (missing importmap:require, missing Encore import, or controllers.json not loaded), images stay invisible or unswapped. Check DevTools for data-controller="lazy-image" on the wrapper.
  • Blurhash must be valid. An empty or malformed hash throws inside the controller, leaving the canvas blank and sometimes the swap never happening. Validate hashes on write (e.g. regex ^[0-9A-Za-z#$%*+,-.:;=?@\[\]^_{|}~]{1,}$) and fall back to a solid color or no hash.
  • width and height are not automatic. Always pass intrinsic dimensions — the component does not sniff them. Without them you reintroduce layout shift, defeating much of the benefit.
  • Avoid lazy-loading above-the-fold content. The hero/LCP image should render eagerly (loading="eager" fetchpriority="high"); lazy-loading it hurts Core Web Vitals. Use the component selectively.
  • Attribute passthrough vs. wrapper. Only <img>-appropriate attributes belong on the component; things like role or aria-* on a wrapper may need a template override. Inspect the rendered DOM once to confirm where each attribute lands.
  • Asset pipeline step after install/update. With AssetMapper, run php bin/console asset-map:compile (and importmap:require again after upgrades); with Encore, run npm run build. Forgetting this is the #1 cause of "it works in dev, images disappear in prod".
  • CSP and the canvas. If you have a strict Content-Security-Policy, the controller's canvas work is fine, but any data-URI placeholder may need img-src data:. Test in a hardened staging environment.
  • Version lock. The package is young (score ~12/36 stars); pin symfony/ux-lazy-image in composer.json and read upgrade notes — the component's prop names or template internals may still shift between minor versions.
  • Debug checklist when images don't appear: (1) is data-controller="lazy-image" in the DOM? (2) is @symfony/ux-lazy-image in importmap.php / package.json? (3) any console error from blurhash? (4) is the image element actually inside the viewport at load (IntersectionObserver won't fire for off-screen ones without scroll)? (5) does the URL resolve (404s render as broken placeholders).
  • Extension point for defaults. Rather than sprinkling class/decoding everywhere, decorate the Twig extension or set defaults in a base template override so future attribute policy changes stay in one place.
  • Not a replacement for image optimization. The package only schedules the load and shows a placeholder — pair it with real image resizing/compression (Imagick, Liip, or a CDN) or you'll still ship enormous files.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core