Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Оптимізація зображень (next/image) у Next.js». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`next/image`** - компонент Next.js, який замінює `<img>` з автоматичною конвертацією у WebP/AVIF, варіантами розмірів через srcset і відкладеним завантаженням за замовчуванням. ```tsx <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority /> ``` **Ключове:** без `sizes` для `fill` зображень браузер завантажує варіант для десктопа навіть на мобільному.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`next/image`** - компонент Next.js для оптимізації зображень, який автоматично конвертує їх у WebP або AVIF, генерує варіанти розмірів через srcset і завантажує їх з відкладенням (lazy loading) за замовчуванням. ## Теорія ### TL;DR - Звичайний `<img>` відправляє оригінальний файл (~2MB) на кожен пристрій; `next/image` нарізає його на варіанти під конкретний екран і доставляє лише потрібний - Головний виграш: 35-50% менші файли на мобільних, бо браузер завантажує 400px WebP замість 2000px JPEG - За замовчуванням: lazy loading, конвертація формату, запобігання CLS через зарезервовані розміри - `priority={true}` тільки для LCP зображень (hero-банери, контент вище згину); максимум 1-2 на сторінку - Зовнішні зображення потребують `remotePatterns` у `next.config.js`, інакше runtime помилка ### Швидкий приклад ```tsx import Image from 'next/image' // Звичайний img: завантажує оригінал 2000x1200px (~2MB) кожного разу <img src="/hero.jpg" alt="Hero" width={2000} height={1200} /> // next/image: видає ~300KB WebP під реальний розмір пристрою, lazy за замовчуванням export default function Hero() { return ( <Image src="/hero.jpg" alt="Hero" width={2000} height={1200} priority // вище згину, тому завантажуємо заздалегідь /> ) } // Мобільний отримає 400w WebP; десктоп - 1200w; браузер обирає через srcset ``` Один момент з практики: якщо не вказати `sizes` для `fill` зображення, браузер вважає його 100vw на всіх брейкпоінтах і завантажує повноширокий варіант навіть для мініатюри в сайдбарі. Невелика помилка, але реальні витрати трафіку. ### Головна різниця Браузер не знає, якого розміру буде `<img>` у верстці, поки не завантажить весь CSS і не прорахує layout. Тому він перестраховується і бере оригінал. `next/image` генерує 8+ варіантів розмірів при білді і виводить елемент `<picture>` зі `srcset` дескрипторами. Браузер обирає потрібний варіант ще до початку завантаження, орієнтуючись на `w` дескриптори і підказку `sizes`. Жодних зайвих байтів. ### Коли що використовувати - Hero та зображення вище згину: додай `priority`, щоб відключити lazy loading і вставити `<link rel="preload">` в `<head>` - Галереї і картки: стандартне відкладене завантаження з `sizes` під реальну ширину (наприклад, `"(max-width: 768px) 100vw, 33vw"`) - Зображення з невідомими розмірами при білді: `fill` всередині позиціонованого батьківського контейнера - Зовнішні URL з Cloudinary, Unsplash, GitHub: налаштуй `remotePatterns` у `next.config.js` - Динамічні зображення зі станом завантаження: `placeholder="blur"` з `blurDataURL` для плавного переходу ### Як це працює під капотом При білді Next.js використовує Sharp для генерації WebP, AVIF і JPEG варіантів у кількох розмірах (приблизно 25%, 50%, 75%, 100% від оригіналу плюс пристроєві брейкпоінти). Результати кешуються у `.next/cache/images`. Для `placeholder="blur"` кодується крихітна base64 blur-мініатюра. На runtime компонент виводить `<picture>` з MIME-типізованими `srcset` записами: спочатку `image/avif`, потім `image/webp`, потім оригінальний формат. Браузер обирає через `Accept` заголовок. AVIF дає приблизно 75% економії відносно JPEG. WebP - близько 30-50%. Next.js пробує AVIF першим через content negotiation, потім WebP, потім JPEG. ISR і SSG використовують той самий кеш, тому повторні запити не потрапляють до Sharp. ### Типові помилки **Відсутність `width` і `height`** ```tsx // Помилка: немає оптимізації, layout shift, помилка в консолі <Image src="/me.jpg" alt="Me" /> // Правильно: завжди вказуй розміри <Image src="/me.jpg" alt="Me" width={800} height={600} /> ``` **`fill` без позиціонованого батьківського контейнера** ```tsx // Помилка: зображення зникає або виходить за межі <div> <Image src="/photo.jpg" fill alt="Photo" /> </div> // Правильно: батьківський елемент потребує position: relative і явних розмірів <div className="relative w-full h-64"> <Image src="/photo.jpg" fill alt="Photo" className="object-cover" /> </div> ``` **Зовнішній URL без `remotePatterns`** ```js // Кидає помилку в рантаймі: "hostname is not configured under images" // Виправлення в next.config.js: module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'images.unsplash.com' } ] } } ``` **`priority` на всіх зображеннях.** Це завантажує все заздалегідь, нівелює lazy loading і уповільнює початкове завантаження сторінки. `priority` потрібен лише для LCP елемента. На більшості сторінок це одне зображення. **`sizes="100vw"` для grid-елементів.** Картка в сітці з трьох колонок рендериться приблизно у 33vw на десктопі. `sizes="100vw"` змушує браузер завантажувати 1200px варіант там, де достатньо 400px. Встанови `sizes` під реальну ширину рендеру. ### Де зустрічається в реальних проектах - Vercel.com: hero-банери з `priority` і breakpoint-специфічними `sizes` - Stripe docs: галереї скриншотів з `fill` і blur placeholder - TailwindUI: картки продуктів з Cloudinary через `remotePatterns` - GitHub: аватари з `avatars.githubusercontent.com`, налаштовані в `remotePatterns` - Звичайний `<img>` підходить для SVG-іконок і base64 даних до 10KB, або коли треба `unoptimized={true}` щоб повністю пропустити pipeline ### Можливі питання на співбесіді **Q:** Як `next/image` обирає між AVIF і WebP? **A:** Перевіряє `Accept` заголовок браузера. Якщо в ньому є `image/avif`, видає AVIF (приблизно 75% економії відносно JPEG). Немає - пробує WebP. Якщо і WebP не підтримується, повертає оригінальний формат. **Q:** Що реально робить проп `sizes`? **A:** Підказує браузеру, якої ширини буде зображення на кожному брейкпоінті, ще до завантаження CSS. Без нього браузер вважає, що зображення займає 100% екрана, і завантажує більший варіант ніж потрібно. Для зображення шириною 300px у сайдбарі `sizes="300px"` суттєво зменшить завантаження. **Q:** Яка різниця між `placeholder="blur"` і `placeholder="empty"`? **A:** `blur` показує розмитий base64 прев'ю поки завантажується повне зображення. `empty` не показує нічого. `blur` підходить для великих hero-зображень, де видимий стан завантаження покращує сприйняту швидкість. `empty` краще для дрібних мініатюр, де розмитий прев'ю виглядав би гірше ніж просто порожнє місце. **Q:** Чому `next/image` іноді суттєво збільшує час білду? **A:** Sharp обробляє кожне унікальне зображення у кожному варіанті розміру. Галерея з 200 зображень і 8 брейкпоінтами дає 1600 операцій Sharp. Допомагає `images.minimumCacheTTL` - кешовані варіанти переживуть деплой. І вибіркове використання `priority` щоб контролювати що обробляється наперед. **Q:** (Senior) У тебе ISR сторінки з динамічними product-зображеннями в S3. Оптимізація запускається на request time, але потрібна відповідь до 100ms. Як вирішити? **A:** Два підходи. Перший: налаштуй кастомний `loader` який веде на CloudFront з Lambda@Edge для ресайзу на льоту; Next.js пропускає власний Sharp, CDN бере на себе обробку. Другий: обмеж кількість варіантів через `deviceSizes` і `imageSizes` у `next.config.js`, потім прогрій кеш пост-деплойним скриптом який проходить по кожному ISR шляху. На Vercel директорія `.next/cache/images` зберігається між деплоями за замовчуванням. ## Приклади ### Базовий: картка профілю з локальним зображенням ```tsx import Image from 'next/image' export default function ProfileCard({ user }) { return ( <div className="flex items-center gap-4"> <Image src="/avatars/default.png" alt={user.name} width={64} height={64} className="rounded-full" /> <p>{user.name}</p> </div> ) } // Next.js видає WebP варіант 64x64; оригінал лишається на диску незміненим ``` `width` і `height` відповідають реальному розміру рендеру, тому зайвого завантаження немає. `priority` не потрібен, цей компонент ніколи не буде вище згину. ### Середній: картка блог-посту з адаптивним fill ```tsx import Image from 'next/image' export default function BlogCard({ post }) { return ( <div className="relative w-full h-64"> <Image src={post.coverImage} alt={post.title} fill sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" className="object-cover rounded-lg" placeholder="blur" blurDataURL={post.blurDataURL} /> <div className="absolute inset-0 bg-gradient-to-t from-black/50" /> </div> ) } // Мобільний: завантажує ~390w WebP // Планшет: ~640w WebP // Десктоп у сітці трьох колонок: ~400w WebP ``` Проп `sizes` тут і є головним. Без нього кожен пристрій завантажує найширший варіант. З ним мобільний отримує файл розміром під мобільний. ### Просунутий: зовнішнє зображення з кастомним loader для Unsplash ```js // next.config.js module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'images.unsplash.com' } ] } } ``` ```tsx import Image from 'next/image' // Кастомний loader передає параметри власного resize API Unsplash function unsplashLoader({ src, width, quality }) { return `${src}?w=${width}&q=${quality || 75}&auto=format` } export default function UnsplashPhoto({ id, alt }) { return ( <Image loader={unsplashLoader} src={`https://images.unsplash.com/photo-${id}`} alt={alt} width={1200} height={800} quality={75} /> ) } // URL на виході: https://images.unsplash.com/photo-123?w=1200&q=75&auto=format // Кастомний loader обходить Sharp в Next.js; ресайзом займається CDN Unsplash ``` Проп `loader` замінює стандартний URL-білдер Next.js. Зручно коли джерело зображень має власний resize API (Cloudinary, Imgix, Unsplash). Конфіг `remotePatterns` потрібен навіть з кастомним loader.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.