Skip to main content
Назад до блогу
4 хв читання

Оптимізація зображень у Next.js 16: що змінилося, за чим тягнутися

Обробка зображень у Next.js значно еволюціонувала. Ось поточний стан next/image, коли його використовувати, коли пропускати, і оптимізації, які реально рухають Lighthouse.

next.jsperformance

Зображення — головна проблема продуктивності більшості сайтів. Одне неоптимізоване hero-зображення може додати 2МБ до початкового завантаження і вронити Lighthouse на 30 пунктів. У Next.js є вбудована оптимізація через next/image, і вона покращується з кожним релізом. Ось де справи в Next.js 16 і що реально має значення.

Що next/image робить за тебе

Компонент <Image> з next/image обробляє чотири речі автоматично:

  1. Конвертація формату. Віддає WebP або AVIF замість PNG/JPEG на основі заголовка Accept браузера. JPEG у 500КБ стає WebP у 120КБ — при тій самій візуальній якості.
  2. Адаптивні розміри. Генерує кілька розмірів і віддає правильний на основі вʼюпорту. Мобільний користувач не завантажує hero-зображення шириною 2400px.
  3. Ліниве завантаження. Зображення нижче фолду завантажуються тільки коли користувач скролить до них. Вбудовано, налаштування IntersectionObserver не потрібне.
  4. Примусові розміри. Вимагає 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. Для оптимізації при збірці (статичні імпорти) це значно збільшує час білду.

Трейдоф:

Налаштування в 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

Чекліст

Для кожного зображення на сторінці:

  1. Використовує <Image> з next/image? Якщо ні — є вагома причина?
  2. Рівно одне зображення вище фолду має priority?
  3. Є проп sizes, що відображає реальний розмір відображення?
  4. Вихідне зображення розумного розміру? (Не пхай фото 6000px у next/image — зменш до 2× максимального розміру відображення)
  5. alt-текст описовий та унікальний?

П'ять питань. Прожени на будь-якій сторінці — зловиш 90% проблем продуктивності зображень.


Хочеш, щоб Next.js-сайт набирав 90+ на Lighthouse? Напиши — оптимізація зображень — частина базової лінії продуктивності, яку я шиплю на кожному проєкті.