Последний месяц я решаю задачу, знакомую многим frontend-разработчикам: React-приложение с галереями, где каждая содержит сотни high-res изображений. Первый же деплой показал, что стандартный подход «кидаем картинки в public/assets» не работает уже на 50+ файлах. Вот что выяснилось на практике.
Local vs. External: когда выносить ассеты
Хранение изображений прямо в проекте — это technical debt в чистом виде. Наблюдения:
- Webpack (и даже Vite) начинает тормозить на сборке уже при 100+ изображениях
- Git репозиторий раздувается, особенно если дизайнеры заливают PNG вместо сжатых WebP
- Худший сценарий — когда в компонентах прямые импорты типа
import img from './assets/gallery/image_42.jpg'
Когда выносить: сразу. Даже для MVP я теперь использую облачное хранилище (S3-совместимое или Firebase Storage), а для production — связку S3 + CDN. Исключение — критически важные статические assets типа логотипа.
CDN и ресайзинг: Cloudinary vs. ручная настройка
Популярный совет — использовать специализированные сервисы типа Cloudinary или Imgix. Они действительно удобны для:
- Динамического ресайза (
/w_300,h_200,q_80/image.jpg) - Конвертации в WebP/AVIF на лету
- Оптимизаций типа
dpr=2для ретины
Но в 2024 году их цена часто избыточна. Альтернатива — свой CDN с трансформациями:
// Пример для AWS Lambda + Sharp
export const handler = async (event) => {
const { key, edits } = event.queryStringParameters;
const image = await s3.getObject({ Bucket: 'my-bucket', Key: key }).promise();
const processed = await sharp(image.Body).resize(edits).toFormat('webp').toBuffer();
return { statusCode: 200, body: processed.toString('base64') };
};
Такой подход окупается при >10K изображений в месяц, но требует настройки кэширования в CloudFront.
Lazy loading без layout shift
Даже с CDN важно правильно загружать изображения. Мой текущий стек:
- Intersection Observer API для настоящего lazy loading (не только ниже fold)
- CSS aspect-ratio + резерв для старых браузеров:
.image-wrapper {
aspect-ratio: 16/9;
@supports not (aspect-ratio: 16/9) {
&::before { float: left; padding-top: 56.25%; content: ''; }
}
}
- Blur-плейсхолдеры через SVG или низкокачественные превью (LQIP):
<img
src="image.webp"
placeholder="data:image/svg+xml;base64,..."
loading="lazy"
/>
Готовые компоненты vs. кастомные решения
Для галерей пробовал:
- react-image-gallery — простой, но негибкий
- react-grid-gallery (на основе CSS Grid) — лучше для masonry-раскладок
- framer-motion для кастомных анимаций переходов
Сейчас склоняюсь к гибридному варианту: беру базовую сетку из пакета и дорабатываю под проект. Например, добавление IntersectionObserver для предзагрузки соседних изображений в lightbox.
Главный урок
Оптимизация изображений — это не про «установи один плагин и забудь». Нужен комплекс:
- Правильное хранение (никаких assets/ в репозитории)
- CDN с трансформациями (готовый сервис или свой вариант)
- Грамотная подгрузка (lazy loading + placeholder)
- Адекватный выбор компонентов
Для небольших проектов достаточно Cloudinary + готового gallery-пакета. Для масштабных — своя инфраструктура с балансом между стоимостью и скоростью.
Попробуй сам: DigitalOcean — $200 кредитов для новых пользователей.
Источник: https://www.reddit.com/r/reactjs/comments/1uuffb6/what_is_the_best_solution_for_heavy_image/