Реализация генерации миниатюр в нескольких размерах
Загружаемые изображения нужно показывать в разных контекстах — карточка товара, превью статьи, аватар, OG-тег. Хранить оригинал и нарезать его на лету при каждом запросе — плохая идея: CPU уходит на рендеринг, латентность растёт, LCP страдает. Правильный путь — генерировать набор фиксированных размеров в момент загрузки и отдавать статику.
Мы специализируемся на оптимизации изображений. Наш подход — генерация миниатюр под ключ с использованием проверенных инструментов. Ниже расскажем, как реализовать это на Laravel/PHP или Node.js/sharp. Мы гарантируем, что ваши страницы загрузятся быстро и пройдут Core Web Vitals.
Типичные проблемы при работе с изображениями
Без системы генерации миниатюр возникают ошибки: 'hydrate-mismatch' из-за разных размеров на клиенте и сервере, высокий LCP из-за загрузки оригиналов, неоправданный расход трафика. Мы решаем эти задачи с помощью предопределённого конфига размеров и конвертации в WebP.
Как выбрать между eager и lazy генерацией?
Eager generation — все размеры создаются сразу при загрузке файла. Подходит, когда набор размеров стабилен и не меняется. Проще логика отдачи, нет промахов кэша.
Lazy generation — размер генерируется по первому запросу, затем кэшируется. Подходит для динамических проектов, где заранее неизвестно, какие размеры понадобятся.
| Характеристика | Eager generation | Lazy generation |
|---|---|---|
| Нагрузка при загрузке | Высокая (много генераций сразу) | Низкая (только сохранение оригинала) |
| Нагрузка при первом запросе | Низкая (файл готов) | Средняя (генерация + кэш) |
| Управление конфигом | Требуется регенерация при изменении | Легко менять конфиг |
| Сложность реализации | Низкая | Средняя (нужен кэш+очередь) |
На большинстве проектов достаточно eager-подхода с фиксированным конфигом.
Стек инструментов
Для PHP/Laravel — библиотека Intervention Image на базе GD или Imagick. Imagick предпочтительнее: лучше обрабатывает EXIF, поддерживает более широкий спектр форматов, точнее работает с цветовыми профилями.
composer require intervention/image // config/image.php return [ 'driver' => 'imagick', // или 'gd' ]; Для Node.js — sharp (libvips под капотом), самый быстрый вариант на рынке:
npm install sharp Сравнение Intervention Image и sharp
| Характеристика | Intervention Image | sharp |
|---|---|---|
| Платформа | PHP | Node.js |
| Драйвер | GD / Imagick | libvips |
| Скорость | Средняя | Очень высокая |
| Поддержка форматов | JPEG, PNG, WebP и др. | JPEG, PNG, WebP, AVIF и др. |
| Простота API | Высокая | Средняя |
| Расход памяти | Выше | Ниже |
Почему стоит генерировать миниатюры сразу в WebP?
Формат WebP поддерживается всеми актуальными браузерами — Chrome, Firefox, Safari (с 2020 года). Он даёт 25–35% экономии веса по сравнению с JPEG при том же визуальном качестве. Для OG-тегов и карточек товаров это снижает LCP на 10–20%. Согласно Google Web Fundamentals, замена JPEG на WebP сокращает время загрузки в среднем на 30%.
Конфигурация размеров
Размеры лучше хранить отдельным конфигом, а не разбрасывать по коду:
// config/thumbnails.php return [ 'sizes' => [ 'thumb' => ['width' => 150, 'height' => 150, 'fit' => 'crop'], 'small' => ['width' => 320, 'height' => null, 'fit' => 'width'], 'medium' => ['width' => 640, 'height' => null, 'fit' => 'width'], 'large' => ['width' => 1280, 'height' => null, 'fit' => 'width'], 'og' => ['width' => 1200, 'height' => 630, 'fit' => 'crop'], ], ]; fit: crop — обрезает с сохранением пропорций по центру. fit: width — масштабирует по ширине, высота пересчитывается.
Сервис генерации
Реализуем сервис, который на основе конфига создаёт все варианты:
namespace App\Services; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Intervention\Image\Facades\Image; class ThumbnailService { public function generateAll(UploadedFile $file, string $basePath): array { $original = Image::make($file); $sizes = config('thumbnails.sizes'); $paths = []; // Сохраняем оригинал $ext = $file->getClientOriginalExtension(); $hash = sha1_file($file->getRealPath()); $originalPath = "{$basePath}/{$hash}.{$ext}"; Storage::disk('public')->put($originalPath, (string) $original->encode()); $paths['original'] = $originalPath; foreach ($sizes as $name => $params) { $img = clone $original; if ($params['fit'] === 'crop') { $img->fit($params['width'], $params['height'], function ($constraint) { $constraint->upsize(); }); } else { $img->resize($params['width'], $params['height'], function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); }); } $sizePath = "{$basePath}/{$hash}_{$name}.webp"; Storage::disk('public')->put($sizePath, (string) $img->encode('webp', 85)); $paths[$name] = $sizePath; } return $paths; } } Генерируем сразу в WebP — качество 85 даёт хороший баланс размера и качества.
Интеграция с моделью и контроллером
В модели Media храним пути всех вариантов в JSON-поле variants. В контроллере загрузки вызываем сервис и сохраняем результат. Пример кода доступен в документации — пишите, мы предоставим полный листинг.
Что делать при изменении конфига размеров?
Если набор размеров меняется, старые файлы нужно перегенерировать. Для этого мы создаём artisan-команду с чанковой обработкой (по 100 записей), которая проходит по всем медиафайлам и пересоздаёт миниатюры. Команда запускается разово и может быть автоматизирована.
Пример команды регенерации
// Artisan command public function handle() { Media::chunk(100, function ($mediaItems) { foreach ($mediaItems as $media) { $this->thumbnailService->regenerate($media); } }); } Что входит в работу
- Разработка конфига размеров под ваш проект
- Реализация сервиса генерации (eager или lazy)
- Интеграция с моделью и контроллером загрузки
- Настройка очередей для фоновой генерации (опционально)
- Регенерация существующих миниатюр при изменении конфига
- Документация и передача доступа к репозиторию
Наши компетенции
Более 5 лет опыта в веб-разработке, 100+ успешных проектов по оптимизации изображений. Мы сертифицированы по Laravel и Node.js. Гарантируем качество и соблюдение сроков.
Ориентировочные сроки
Настройка библиотеки, конфига размеров, сервиса генерации — 4–6 часов. Интеграция с моделью, загрузчик, команда регенерации — ещё 3–4 часа. Если нужна очередь (генерация в фоне через Job) — плюс 2 часа.
Закажите реализацию под ключ: свяжитесь с нами для оценки вашего проекта. Мы подготовим индивидуальное предложение без скрытых платежей.







