Оптимизация изображений: WebP, srcset, lazy loading, blur placeholder, CDN. Use when: медленная загрузка, большие изображения, оптимизация LCP.
Скилл для оптимизации загрузки и отображения изображений. Цель: быстрый LCP, минимальный трафик, плавный UX.
loading="lazy" для below-the-foldwidth + для предотвращения CLSheight<link rel="preload"> для LCP-изображенияimport { useState, useCallback } from 'react'
interface OptimizedImageProps {
src: string
alt: string
width: number
height: number
className?: string
priority?: boolean
sizes?: string
}
export function OptimizedImage({
src, alt, width, height, className, priority, sizes
}: OptimizedImageProps) {
const [loaded, setLoaded] = useState(false)
const [err, setErr] = useState(false)
const handleLoad = useCallback(() => setLoaded(true), [])
const handleError = useCallback(() => setErr(true), [])
if (err) {
return (
<div
className={cn('flex items-center justify-center bg-muted', className)}
style={{ width, height }}
>
<ImageOff className="h-8 w-8 text-muted-foreground" />
</div>
)
}
return (
<div className={cn('relative overflow-hidden', className)}>
{!loaded && (
<div
className="absolute inset-0 animate-pulse bg-muted"
style={{ aspectRatio: `${width}/${height}` }}
/>
)}
<img
src={src}
alt={alt}
width={width}
height={height}
loading={priority ? 'eager' : 'lazy'}
decoding={priority ? 'sync' : 'async'}
onLoad={handleLoad}
onError={handleError}
sizes={sizes || '100vw'}
className={cn(
'transition-opacity duration-300',
loaded ? 'opacity-100' : 'opacity-0'
)}
/>
</div>
)
}
function getImageUrl(path: string, w: number, q = 80): string {
const { data } = supabase.storage
.from('images')
.getPublicUrl(path, {
transform: { width: w, quality: q, format: 'webp' },
})
return data.publicUrl
}
// Использование в srcset
function getSrcSet(path: string): string {
return [320, 640, 1024, 1440]
.map(w => `${getImageUrl(path, w)} ${w}w`)
.join(', ')
}
// В head страницы — для hero-изображения
<link
rel="preload"
as="image"
href={heroImageUrl}
fetchPriority="high"
/>
width и height заданы (нет CLS)loading="lazy"loading="eager" + preloadalt текст заполнен на каждом <img>