Зображення — головна проблема продуктивності більшості сайтів. Одне неоптимізоване hero-зображення може додати 2МБ до початкового завантаження і вронити Lighthouse на 30 пунктів. У Next.js є вбудована оптимізація через next/image, і вона покращується з кожним релізом. Ось де справи в Next.js 16 і що реально має значення.
Що next/image робить за тебе
Компонент <Image> з next/image обробляє чотири речі автоматично:
- Конвертація формату. Віддає WebP або AVIF замість PNG/JPEG на основі заголовка Accept браузера. JPEG у 500КБ стає WebP у 120КБ — при тій самій візуальній якості.
- Адаптивні розміри. Генерує кілька розмірів і віддає правильний на основі вʼюпорту. Мобільний користувач не завантажує hero-зображення шириною 2400px.
- Ліниве завантаження. Зображення нижче фолду завантажуються тільки коли користувач скролить до них. Вбудовано, налаштування IntersectionObserver не потрібне.
- Примусові розміри. Вимагає
widthтаheight(абоfill), що запобігає зсуву лейауту — браузер резервує місце до завантаження зображення.
Це базовий рівень. Використовуй <Image> для кожного зображення, якщо немає конкретної причини не робити цього.
Проп priority: використовуй рівно один раз
Проп priority вимикає ліниве завантаження і попередньо завантажує зображення. Має бути на найбільшому зображенні вище фолду — зазвичай hero-зображення.
<Image src="/hero.jpg" alt="Hero" width={1200} height={630} priority />
Тільки одне зображення має мати priority. Я бачив кодові бази, де кожне зображення в першій секції має priority. Результат: браузер намагається попередньо завантажити 6 зображень одночасно, конкуруючи за пропускну здатність, і жодне не завантажується швидко. Саме це спричинило падіння Lighthouse на 20 пунктів на одному з моїх проєктів — зображення нижче фолду з priority, що попередньо завантажувалося раніше hero.
Статичні імпорти vs рядкові шляхи
Два способи посилатися на зображення:
// Статичний імпорт — оптимізується при збірці
import heroImage from '@/public/hero.jpg'
<Image src={heroImage} alt="Hero" />
// Рядковий шлях — оптимізується за запитом
<Image src="/hero.jpg" alt="Hero" width={1200} height={630} />
Статичні імпорти кращі, коли можливо. Отримуєш автоматичний вивід width/height, блюр-плейсхолдери та оптимізацію при збірці. Рядкові шляхи вимагають явних розмірів і оптимізують за запитом.
Для CMS-зображень або завантаженого користувачами контенту — рядкові шляхи (або віддалені URL з remotePatterns) єдиний варіант. Для всього в репозиторії — використовуй статичні імпорти.
Проп sizes важливіший, ніж здається
Без sizes Next.js генерує зображення на дефолтних брейкпоінтах, і браузер обирає на основі повної ширини вʼюпорту. Це часто неправильно.
Якщо зображення в 3-колонковій сітці — воно ніколи не ширше ~33% вʼюпорту. Але без sizes браузер припускає, що може бути 100%, і завантажує зображення більше, ніж потрібно.
// У 3-колонковій сітці
<Image
src={project.image}
alt={project.title}
width={400}
height={300}
sizes="(max-width: 768px) 100vw, 33vw"
/>
Це каже браузеру: «На мобільному зображення на всю ширину. На десктопі — одна третина.» Браузер завантажує відповідний розмір. На реальному проєкті це скоротило загальний трансфер зображень на 40% без візуальних змін.
AVIF vs WebP
Next.js може віддавати AVIF, який на 20–30% менший за WebP при тій самій якості. Але кодування AVIF повільне — у 5–10 разів повільніше за WebP. Для оптимізації при збірці (статичні імпорти) це значно збільшує час білду.
Трейдоф:
- Мало зображень (портфоліо, лендинг): вмикай AVIF. Удар по часу збірки малий, економія розміру накопичується для повторних відвідувачів.
- Багато зображень (блог зі скріншотами, e-commerce): залишайся на WebP. Час збірки важливий, і 20% економії на зображення не виправдовує 5× повільніший білд.
Налаштування в next.config.js:
module.exports = {
images: {
formats: ['image/avif', 'image/webp'], // AVIF перший, WebP фолбек
},
}
Блюр-плейсхолдери
Блюр-плейсхолдери показують низькорезолюційну версію зображення під час завантаження, запобігаючи «порожній блок, потім раптове зображення». Статичні імпорти отримують це безкоштовно:
import heroImage from '@/public/hero.jpg'
;<Image src={heroImage} alt="Hero" placeholder="blur" />
Для динамічних зображень потрібно генерувати блюр-дані самостійно. Бібліотека plaiceholder робить це при збірці:
import { getPlaiceholder } from 'plaiceholder'
const { base64 } = await getPlaiceholder(imageBuffer)
<Image src={url} alt="..." placeholder="blur" blurDataURL={base64} />
Варте зусиль для hero-зображень і контенту вище фолду. Не варте для сіток мініатюр, де сірий плейсхолдер достатній.
Коли пропускати next/image
- SVG-іконки та логотипи.
next/imageпроганяє SVG через оптимізатор без потреби. Використовуй звичайний<img>або інлайн SVG. - Крихітні зображення (<5КБ). Оверхед оптимізації перевищує економію. Інлайн як base64 або звичайний
<img>. - Фонові зображення через CSS.
next/imageвимагає DOM-елемент. Для CSSbackground-image— оптимізуй вручну і віддавай з/public. - OG-зображення. Вони віддаються краулерам, не користувачам. Генеруй окремо через
next/og.
Чекліст
Для кожного зображення на сторінці:
- Використовує
<Image>зnext/image? Якщо ні — є вагома причина? - Рівно одне зображення вище фолду має
priority? - Є проп
sizes, що відображає реальний розмір відображення? - Вихідне зображення розумного розміру? (Не пхай фото 6000px у
next/image— зменш до 2× максимального розміру відображення) alt-текст описовий та унікальний?
П'ять питань. Прожени на будь-якій сторінці — зловиш 90% проблем продуктивності зображень.
Хочеш, щоб Next.js-сайт набирав 90+ на Lighthouse? Напиши — оптимізація зображень — частина базової лінії продуктивності, яку я шиплю на кожному проєкті.