Генерація мініатюр зображень
Генерація мініатюр перетворює завантажені зображення на кілька розмірів для різних контекстів (список, карточка, повноразмірний перегляд) без втрати оригіналу.
Laravel: Intervention Image + черга
// Модель з автоматичною генерацією мініатюр
class Image extends Model
{
const SIZES = [
'thumb' => [200, 200],
'medium' => [600, 400],
'large' => [1200, 800],
];
}
// Job для асинхронної генерації
class GenerateImageThumbnails implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
public function __construct(private Image $image) {}
public function handle(): void
{
$originalPath = Storage::disk('s3')->path($this->image->path);
$img = \Intervention\Image\Facades\Image::make($originalPath);
foreach (Image::SIZES as $size => [$width, $height]) {
$resized = clone $img;
$resized->fit($width, $height); // crop по центру
$thumbPath = str_replace('original/', "{$size}/", $this->image->path);
Storage::disk('s3')->put($thumbPath, $resized->encode('webp', 85)->__toString());
}
$this->image->update(['processed' => true]);
}
}
// Контроллер завантаження
public function store(Request $request): JsonResponse
{
$path = Storage::disk('s3')->putFile('original', $request->file('image'));
$image = Image::create([
'path' => $path,
'user_id' => auth()->id(),
'processed' => false,
]);
GenerateImageThumbnails::dispatch($image);
return response()->json(['id' => $image->id]);
}
Node.js: Sharp
Sharp — найшвидша бібліотека Node.js для обробки зображень (на базі libvips).
import sharp from 'sharp';
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
const SIZES = {
thumb: { width: 200, height: 200 },
medium: { width: 600, height: 400 },
large: { width: 1200, height: 800 },
} as const;
async function generateThumbnails(s3Key: string): Promise<Record<string, string>> {
const s3 = new S3Client({ region: 'eu-west-1' });
// Завантажити оригінал
const { Body } = await s3.send(new GetObjectCommand({
Bucket: process.env.S3_BUCKET!,
Key: s3Key,
}));
const buffer = Buffer.from(await (Body as any).transformToByteArray());
const results: Record<string, string> = {};
await Promise.all(
Object.entries(SIZES).map(async ([name, { width, height }]) => {
const thumbnail = await sharp(buffer)
.resize(width, height, { fit: 'cover', position: 'centre' })
.webp({ quality: 85 })
.toBuffer();
const thumbKey = s3Key.replace('original/', `${name}/`).replace(/\.[^.]+$/, '.webp');
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET!,
Key: thumbKey,
Body: thumbnail,
ContentType: 'image/webp',
CacheControl: 'public, max-age=31536000',
}));
results[name] = thumbKey;
})
);
return results;
}
Ледаяча генерація через Glide (PHP)
Glide генерує мініатюри за запитом із підписаним URL:
// Маршрут для зображень
Route::get('/img/{path}', function (Request $request, string $path) {
$server = League\Glide\ServerFactory::create([
'source' => Storage::disk('s3')->getDriver(),
'cache' => Storage::disk('local')->getDriver(),
'cache_path_prefix' => '.cache',
'base_url' => '/img',
'max_image_size' => 2000 * 2000,
]);
// Перевірка підпису URL
League\Glide\Signatures\SignatureFactory::create(config('app.key'))
->validateRequest('/img/' . $path, $request->all());
return $server->getImageResponse($path, $request->all());
})->where('path', '.*');
// Генерація підписаного URL
$url = (new League\Glide\Urls\UrlBuilderFactory)
->create('/img', config('app.key'))
->getUrl('uploads/photo.jpg', ['w' => 400, 'h' => 300, 'fit' => 'crop']);
Формати та оптимізація
// Вибір формату за підтримкою браузером
const output = sharp(buffer)
.resize(800)
.toFormat(supportsAvif ? 'avif' : supportsWebp ? 'webp' : 'jpeg', {
quality: supportsAvif ? 60 : supportsWebp ? 80 : 85,
});
AVIF дає 50% економію відносно JPEG при тій же якості. WebP підтримується всіма сучасними браузерами. Для максимальної сумісності використовують <picture> з кількома форматами.
Терміни реалізації
Генерація мініатюр в черзі (Laravel Job або BullMQ Worker) із збереженням в S3: 2–3 дні. З ленивою генерацією через Glide та кешуванням CDN: 3–4 дні.







