Image loading
ProgressiveImage wraps next/image with a custom loader and a graceful reveal:
animated skeleton → blur placeholder → full image
└─ opacity fade, 150ms ease-out
Zero layout shift
Every image's intrinsic dimensions are generated up front, so the layout engine assigns an exact box before anything loads. The parent box is sized; the image fills it. Nothing reflows.
interface ImageData {
id: string; width: number; height: number; // known before render → zero CLS
blurDataURL: string; color: string; url: string; alt: string;
}The picsum loader
src encodes the seed + intrinsic aspect ratio, so a width-only loader can request any
responsive size while preserving the ratio and serving the same photo deterministically:
function picsumLoader({ src, width }) {
const [seed, w, h] = src.split("|");
const height = Math.round(width * (Number(h) / Number(w)));
return `https://picsum.photos/seed/${seed}/${width}/${height}`;
}Skeleton, priority, and prefetch
- Skeleton. While unloaded, a transform-only shimmer sweeps across the tile (GPU, disabled under reduced motion), removed the moment the image loads.
- Priority. First-viewport tiles load eagerly (
priority); everything else lazy-loads. - Predictive prefetch. During
requestIdleCallback, upcoming images are fetched and pre-decoded withimg.decode()so they paint without decode jank.
export function preloadAndDecode(image, width) {
const el = new Image();
el.src = picsumLoader({ src: picsumSrc(image), width });
return el.decode().catch(() => undefined);
}