Реалізація генерації мініатюр у кількох розмірах
Ви завантажуєте зображення, але їх потрібно показувати в різних контекстах — картка товару, прев'ю статті, аватар, OG-тег. Зберігати оригінал і нарізати його на льоту при кожному запиті — погана ідея: CPU витрачається на рендеринг, латентність зростає, LCP страждає. Правильний шлях — генерувати набір фіксованих розмірів у момент завантаження і віддавати статику.
Ми спеціалізуємося на оптимізації зображень. Наш підхід — генерація мініатюр під ключ з використанням перевірених інструментів. Нижче розповімо, як реалізувати це на Laravel/PHP або Node.js/sharp. Ми гарантуємо, що ваші сторінки завантажаться швидко і пройдуть Core Web Vitals.
Типові проблеми при роботі із зображеннями
Без системи генерації мініатюр виникають помилки: 'hydrate-mismatch' через різні розміри на клієнті та сервері, високий LCP через завантаження оригіналів, невиправдана витрата трафіку. Ми вирішуємо ці задачі за допомогою попередньо визначеного конфігу розмірів і конвертації в WebP.
На одному з проектів інтернет-магазину ми впровадили генерацію мініатюр з WebP. Це зменшило LCP з 4.2с до 1.8с, а обсяг трафіку на сторінках каталогу скоротився на 35%.
Як вибрати між 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)
- Інтеграція з моделлю та контролером завантаження
- Налаштування черг для фонової генерації (опціонально)
- Регенерація існуючих мініатюр при зміні конфігу
- Документація та передача доступу до репозиторію
Наші компетенції
Понад 10 років досвіду в веб-розробці, 40+ успішних проектів з оптимізації зображень. Ми сертифіковані по Laravel та Node.js. Гарантуємо якість і дотримання строків.
Орієнтовні строки
Налаштування бібліотеки, конфігу розмірів, сервісу генерації — 4–6 годин. Інтеграція з моделлю, завантажувач, команда регенерації — ще 3–4 години. Якщо потрібна черга (генерація у фоні через Job) — плюс 2 години.
Замовте реалізацію під ключ: зв'яжіться з нами для оцінки вашого проекту. Ми підготуємо індивідуальну пропозицію без прихованих платежів.







