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? Напиши — оптимизация изображений — часть базовой линии производительности, которую я шиплю на каждом проекте.