Skip to content

Migrating from v1 to v2

Most users need a single find-and-replace. This page documents every change that can affect existing integrations.

Breaking Changes

loadImage Removal

The deprecated loadImage alias is gone. Replace every call site with triggerLoad:

diff
- import { loadImage } from 'unlazy'
- loadImage(image, onLoad, onError)
+ import { triggerLoad } from 'unlazy'
+ triggerLoad(image, { onImageLoad: onLoad, onImageError: onError })

triggerLoad Signature

triggerLoad now takes an options object instead of positional callbacks, and returns a cleanup function that detaches listeners and (for standalone images) aborts the in-flight network fetch:

diff
- triggerLoad(image, onLoad, onError)
+ const cleanup = triggerLoad(image, { onImageLoad: onLoad, onImageError: onError })
+ // Optional: cleanup() to cancel before the load completes

autoSizes Owns Ongoing Size Tracking

triggerLoad is one-shot again – it no longer accepts updateSizesOnResize. Ongoing re-resolution of data-sizes="auto" lives on autoSizes, which now accepts { updateOnResize: true } and returns a cleanup function:

diff
- triggerLoad(image, { updateSizesOnResize: true })
+ const cleanupSizes = autoSizes(image, { updateOnResize: true })
+ const cleanupLoad = triggerLoad(image)
+ // Later: cleanupSizes(); cleanupLoad()

For the common case, lazyLoad keeps updateSizesOnResize and delegates to autoSizes internally – no caller change needed.

autoSizes itself now always returns a function. With no options, the returned cleanup is a no-op; with updateOnResize: true, it disconnects every ResizeObserver created by the call. Passing an <img> inside a <picture> walks to every <source data-sizes="auto"> sibling in the same call, replacing the previous need to invoke autoSizes separately on each source.

isLazyLoadingSupported Removal

Native loading="lazy" is Baseline Widely Available; the feature-detect was used internally to fall back to immediate swap, and that branch is gone. If you imported the helper directly, replace it with:

ts
const isLazyLoadingSupported = 'loading' in HTMLImageElement.prototype

…or assume true for any browser in your support matrix.

Expanded Default Selector

The default selector was:

img[loading="lazy"]

It is now:

img[loading="lazy"], img[loading="eager"][data-src], img[loading="eager"][data-srcset]

If you ever wrote <img loading="eager" data-src="..."> in v1, unlazy silently ignored it. In v2, unlazy processes it through the new eager-priority path (see below). If you were relying on the silent no-op, pass a custom selector.

Native error Event on Preload Failure

Previously, when unlazy's off-DOM preload failed (404, decode error), the visible <img> never saw it – only the onImageError callback did. v2 dispatches a synthetic error event on the visible <img>, so native onerror / @error / onError now work for setting a fallback src:

html
<img data-src="hero.jpg" onerror="this.src='/fallback.jpg'">

If you bind both onImageError and a native error listener, both fire – pick whichever surface fits your app.

Picture-Element Callback Parity

onImageLoad and onImageError now fire once when the browser resolves a source on <img> elements inside <picture>. No code change required.

New Behavior

Eager-Priority Path

Images with loading="eager" and a data-src or data-srcset attribute get the above-the-fold treatment – see lazyLoad → How It Works for the exact behavior and the Core Web Vitals guide for when to reach for it.

Use this for your hero / LCP image:

html
<img
  loading="eager"
  data-src="hero.jpg"
  width="1600"
  height="900"
  alt="Sunset"
>

Idempotent lazyLoad()

Calling lazyLoad() twice on an already-processed image used to log a spurious Missing data-src or data-srcset error because the first call removed those attributes. In v2, processed images are tracked and silently skipped on subsequent calls.

Result: you can safely re-invoke lazyLoad() after inserting images into the DOM without console noise.

Dev-Mode LCP Warning

unlazy now warns in development when the LCP element is still configured for lazy loading. See the Core Web Vitals guide.

sources Prop Across Adapters

The sources prop is no longer Nuxt-only. Every adapter now renders a <picture> when you pass an array of UnLazySource objects:

ts
import type { UnLazySource } from 'unlazy'

const sources: UnLazySource[] = [
  { type: 'image/avif', srcSet: 'hero.avif 1x, hero@2x.avif 2x' },
  { media: '(max-width: 600px)', srcSet: 'hero-mobile.jpg', width: 480, height: 640 },
]

Each entry becomes a <source> child with type, media, width, height, and data-sizes="auto" support. Pass updateSizesOnResize: true to lazyLoad (or { updateOnResize: true } directly to autoSizes) to re-resolve <source data-sizes="auto"> siblings on viewport resize.

Vue / Nuxt: @image-load and @image-error Emits

The Vue and Nuxt adapters now expose a symmetric pair of emits aligned with the core option names:

v1v2
@loaded="(image) => …"@image-load="(image) => …"
@image-error="(image, error) => …"
diff
- <UnLazyImage @loaded="onLoaded" />
+ <UnLazyImage @image-load="onImageLoad" @image-error="onImageError" />

The declared @error emit was removed.

INFO

Existing @error listeners keep working via Vue's attribute fallthrough – they fire for native errors and for the synthetic preload failure. Reach for @image-error only when you want both the wrapped image element and the event.

Installation

sh
pnpm add unlazy@2

Released under the MIT License.