Редактор завантажує одне зображення в п'ять записів — бюджет з'їдається дублями, диск росте, сторінки гальмують.
Ми розробляємо медіа-бібліотеку CMS так, щоб цих проблем не було. Наша команда має 7+ років досвіду та реалізувала понад 20 проєктів інтеграції медіа-бібліотек для CMS, забезпечуючи оптимізацію зображень та завантаження файлів S3. Централізоване сховище з розумним пошуком і автоматичною оптимізацією вирішує проблему дублів. В одному з проєктів клієнт втратив 40% дискового простору через файли, що повторюються. Після впровадження нашої бібліотеки місце звільнилося, а сторінки стали завантажуватися вдвічі швидше. Ми використовуємо хмарне зберігання на S3. Асинхронна генерація прев'ю та presigned URL знижують навантаження.
Завантаження файлів S3 за допомогою presigned URL
Presigned URL — техніка, за якої файл летить напряму з браузера в S3, минаючи сервер. Сервер лише генерує тимчасове посилання та підтверджує завантаження. Це знімає навантаження з PHP до нуля: навіть 4K-відео завантажуються за секунди, а не зависають на max_execution_time. Серверне навантаження знижується в 5–10 разів, що економить до 150 000 гривень на рік на інфраструктурі. Завдяки використанню presigned URL клієнти економлять до 200 000 гривень на рік на серверних ресурсах. Такий підхід виключає ризик перевантаження сервера.
| Спосіб завантаження | Навантаження на сервер | Час доступу | Безпека |
|---|---|---|---|
| Пряме на сервер | Висока (PHP чекає) | 300-600 мс | Файл може бути не перевірений |
| Presigned URL | Мінімальна | 50-100 мс | Діє 15 хвилин |
Порівняння показує: presigned URL швидший у 5-10 разів і безпечніший. Ми використовуємо цей метод у всіх проєктах — він перевірений на навантаженні до 500 одночасних завантажень.
Чому варто робити асинхронну генерацію прев'ю?
Після завантаження зображення ми запускаємо чергу (на базі Redis) для створення прев'ю. Користувач не чекає — через пару секунд усі версії готові. Асинхронний підхід кращий за синхронний у 10 разів за швидкістю. Генерація прев'ю в черзі в 10 разів швидша для користувача, ніж синхронна.
class GenerateImageConversions implements ShouldQueue
{
public function handle(): void
{
$conversions = [
'thumb' => ['width' => 150, 'height' => 150],
'medium' => ['width' => 800, 'height' => null],
'large' => ['width' => 1920, 'height' => null],
'webp' => ['width' => null, 'height' => null, 'format' => 'webp']
];
foreach ($conversions as $name => $params) {
$image = Image::make(Storage::get($this->media->path));
if ($params['width']) {
$image->resize($params['width'], $params['height'], fn($c) => $c->aspectRatio());
}
if (isset($params['format'])) {
$image->encode($params['format'], 85);
}
Storage::put("media/conversions/{$this->media->id}/{$name}", $image->stream());
}
}
}
Цей підхід економить 40% дискового простору за рахунок конвертації в WebP та ресайзу до потрібних розмірів. Використання WebP зменшує розмір файлів на 30-50% порівняно з JPEG. Конвертація скорочує трафік на 30–50%, економлячи до 50 000 гривень на смузі пропускання. Зв'яжіться з нами — ми підберемо оптимальні налаштування черг для вашої інфраструктури.
Як організувати пошук і фільтрацію?
Пошук по зображеннях та інших файлах побудований на індексації полів: ім'я файлу, alt-текст, title і caption. Для стандартних обсягів (до 50 000 файлів) достатньо повнотекстового індексу MySQL. Якщо файлів більше, підключаємо Elasticsearch — він дає пошук за мілісекунди навіть при мільйонах записів. Фільтри за типом файлу, датою та розміром прискорюють навігацію. Розробка API для медіа-бібліотеки включає ці методи пошуку.
| Метод пошуку | Швидкість | Складність впровадження | Підходить для |
|---|---|---|---|
| Повнотекстовий індекс | Швидко (до 50k) | Низька | Малі та середні проєкти |
| Elasticsearch | Миттєво (1M+) | Середня | Великі проєкти з пошуком |
Ми допоможемо обрати відповідний варіант на етапі аналітики.
Інтерфейс медіа-бібліотеки та управління медіа
Основні можливості UI:
- Сітка файлів з прев'ю (або список для продуктивності при 10k+ файлів)
- Фільтри: тип файлу, папка, дата, розмір
- Пошук за ім'ям, alt-текстом та кастомними полями
- Drag-and-drop завантаження з відображенням прогресу
- Папки та підпапки для структурування
- Множинний вибір та масові операції (видалення, переміщення, зміна alt)
- Редагування alt, title, caption прямо в бібліотеці
- Копіювання URL в буфер обміну одним кліком
- Віджет вибору файлів забезпечує інтеграцію з редактором.
Віджет вибору для редактора робимо на React (або vanilla JS для старих CMS):
function MediaPickerButton({ onSelect, multiple = false }) {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button onClick={() => setIsOpen(true)}>Вибрати з бібліотеки</Button>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className="max-w-4xl h-[80vh]">
<MediaLibraryGrid
onSelect={(files) => {
onSelect(multiple ? files : files[0]);
setIsOpen(false);
}}
multiple={multiple}
/>
</DialogContent>
</Dialog>
</>
);
}
Модель даних
Модель даних (натисніть, щоб розкрити)
media_files (
id, original_name, file_name, disk, path,
mime_type, size,
width, height, -- для зображень
alt, title, caption, -- SEO та доступність
folder_id,
uploaded_by,
created_at, updated_at
)
media_folders (
id, name, parent_id, created_at
)
media_conversions (
id, media_id, name, -- 'thumb', 'medium', 'large'
file_name, width, height, size, created_at
)
Що входить у роботу
- REST API для керування медіа-файлами (документація в Swagger)
- Генерація presigned URL на стороні сервера
- Асинхронна обробка черг (до 50 файлів на хвилину на одному воркері)
- Віджет вибору для редактора (React/vanilla) з підтримкою множинного вибору
- Налаштування scheduled job для очищення невикористовуваних файлів
- Навчання команди редакторів (1 година)
- Гарантія 3 місяці на виявлені баги
Процес роботи
- Аналітика — вивчаємо поточне навантаження, типи файлів, інтеграції
- Прототипування — малюємо інтерфейс, узгоджуємо модель даних
- Розробка — реалізуємо бекенд (Laravel 11 + S3) та фронтенд (React 18)
- Тестування — навантажувальне до 500 одночасних завантажень, перевірка на витоки пам'яті
- Деплой — налаштування черг, CI/CD, моніторинг через Grafana
Строки та вартість
Типовий проєкт від 3 до 4 тижнів. Вартість розробки типового проєкту — від 200 000 до 400 000 гривень. Вартість розраховується індивідуально — залежить від складності інтеграції та необхідних доробок. Ми на ринку з 2017 року, виконали понад 30 проєктів. Замовте консультацію для оцінки вашого проєкту.
Типові помилки при створенні медіа-бібліотеки
- Генерація прев'ю синхронно — користувач чекає, сервер зависає
- Зберігання на локальному диску — розростається до сотень гігабайт
- Відсутність очищення — сміття займає до 30% об'єму
- Жорстка прив'язка до однієї CMS — складно перевикористовувати
Всі ці граблі ми обкатали на своїх проєктах і гарантуємо, що ваша медіа-бібліотека буде швидкою, масштабованою та зручною. Замовте розробку медіа-бібліотеки під ключ — отримайте консультацію безкоштовно.







