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