0% прочитано

Laravel Media Explorer: як я перестала прокручувати сотні папок

Коли в Media Explorer стало близько 60 папок для публікацій, просте прокручування вже почало заважати. Для сотень або тисяч папок такий список взагалі не масштабується. Розбираю нові папки першими, пошук на сервері, режими сортування, пагінацію, природне сортування назв, збереження стану провідника та віртуальне групування без зміни реальної структури Media.

21 вересня 2026 р. 12 хв читанняЛаравель

Коли в Media Explorer у ICanUp стало близько 60 папок для публікацій, я помітила дуже просту проблему: нову папку доводилося щоразу шукати прокручуванням списку.

На такій кількості папок це ще можна терпіти. На чотирьохстах це вже поганий інтерфейс. На тисячі папок плоский список перетворюється на проблему і для користувача, і для клієнтської частини.

Я не хотіла просто додати ще один великий scroll area. Потрібно було змінити сам контракт навігації: нові папки першими, пошук без завантаження всієї колекції, передбачуване сортування та порційне отримання даних.

Масштабування Media Explorer починається не тоді, коли список уже містить 1000 папок. Воно починається тоді, коли користувач уперше змушений шукати потрібну папку прокручуванням.
Laravel Media Explorer scaling diagram showing newest folders search sorting pagination and virtual grouping instead of one huge flat folder list
Плоский список добре працює лише до певного масштабу. Далі потрібні сортування, пошук і порційне завантаження.

Laravel Media Explorer з сотнями папок: чому flat list перестає працювати

Проблема великого списку не обмежується довгим прокручуванням.

Якщо завантажити всі папки відразу, одночасно ростуть:

  • обсяг відповіді API;
  • кількість елементів у DOM;
  • вартість повторного рендерингу;
  • час пошуку потрібної папки очима;
  • ризик випадково скинути стан під час наступного завантаження;
  • складність роботи на мобільному пристрої.
Плоский список не масштабується нескінченно
Small collection: root├── folder 1├── folder 2├── folder 3└── folder 4  Large collection: root├── hundreds of folders├── more folders├── more folders└── user keeps scrolling...

Нові папки першими: created_at DESC як базовий порядок

У моєму сценарії найчастіше потрібна не найстаріша папка, а та, яку я щойно створила для нового матеріалу.

Тому базовий порядок змінився на created_at DESC: нові папки знаходяться зверху без додаткової взаємодії.

PHP - Спрощений контракт Newest
// Simplified query contract. $folders = $query    ->orderByDesc('created_at')    ->orderByDesc('id');

Другий критерій потрібен для стабільності, якщо кілька рядків мають однаковий час створення.

Конкретна реалізація tie-breaker може відрізнятися, але результат не повинен хаотично міняти порядок між запитами.

Зміна порядку папок не повинна випадково змінювати чинний контракт сортування самих Media-файлів усередині папки.

Сортування папок: Newest, Oldest, Name A-Z і Name Z-A

Режим

Контракт

Newest

created_at DESC, режим за замовчуванням

Oldest

created_at ASC

Name A-Z

Природне сортування назв за зростанням

Name Z-A

Природне сортування назв у зворотному порядку

Важливо, щоб цей вибір не існував окремо в кожному списку або grid-компоненті.

Media Explorer має один спільний контракт сортування, а поточний режим зберігається в стані сторінки або поточної сесії Explorer.

Natural sorting числових назв має залишитися в режимі Name

До масштабування списку я вже окремо виправляла випадок, коли цифрові назви сортувалися як 1, 10, 11, 2.

Тому додавання режимів Newest і Oldest не повинно повертати стару проблему. У режимі Name зберігається природне сортування числових назв.

Природне сортування залишається частиною Name mode
Name A-Z: 123101120 not: 11011220

Коли колекція велика, пошук тільки на клієнтській частині вирішує не ту проблему.

Щоб відфільтрувати 1000 папок у браузері, спочатку довелося б отримати всі 1000 папок через API.

Тому пошук має виконуватися на сервері або іншим способом, який не вимагає передавати всю колекцію клієнту.

Пошук має зменшувати набір до передачі клієнту
Wrong: API -> load every folder          |          vfrontend filters 1000+ rows  Scalable: query  |  vserver filters folders  |  v

Debounce і Clear потрібні навіть для простого folder search

Я не хочу надсилати окремий HTTP-запит на кожен символ під час швидкого введення.

Тому поле пошуку має debounce, а очищення запиту повинно повертати користувача до звичайного стану списку без повного перезавантаження Media Explorer.

Результат пошуку має відкрити папку і не втратити breadcrumbs

Знайти назву недостатньо.

Після вибору результату користувач має потрапити всередину папки так само, ніби він пройшов до неї звичайною навігацією.

Поточна папка та ланцюжок навігації повинні залишитися узгодженими.

Пошук не створює окремий режим навігації
folder search    |    vmatching folder    |    vopen folder    |    +--> current folder updated    +--> breadcrumbs updated    +--> children loaded

Pagination і lazy loading замість одного великого folder response

Кореневий рівень Media Explorer більше не повинен означати "завантажити все, що існує".

Клієнт отримує тільки потрібну сторінку sibling folders, а наступні записи довантажуються за потреби.

Порційне завантаження sibling folders
root folders page 1    |    vrender current folders    |    vneed more?    |    +--> request next page    |    vappend without replacing previous entries

Load more має append, а не замінювати вже завантажені записи

Це особливо важливо, бо в Media Explorer вже був реальний клас помилки, де наступна сторінка успішно приходила з API, але дані не додавалися до попереднього списку.

Для папок контракт такий самий: попередні записи залишаються, нова сторінка додається, дублікати не з’являються, а metadata визначає, чи є ще дані.

JavaScript - Спрощений приклад безпечного append
const existingKeys = new Set(    entries.map(        entry => `${entry.type}-${entry.id}`    )); const nextEntries = response.data.filter(    entry => ! existingKeys.has(        `${entry.type}-${entry.id}`    )); entries.push(...nextEntries);
Це ілюстрація контракту append і deduplication, а не твердження про точну форму поточного store-коду.

Відкриття папки має завантажувати тільки потрібних children

Ієрархічний Media Explorer не повинен заздалегідь отримувати весь tree.

Коли користувач відкриває папку, сервер може завантажити дочірні записи саме поточного parent_id і тільки потрібну сторінку.

Це зберігає ізоляцію між рівнями дерева й не переносить ціну всієї Media hierarchy на кожне відкриття Explorer.

Створення папки не повинно скидати поточний стан Media Explorer

Після додавання пошуку, сортування й пагінації легко зробити іншу помилку: після Create Folder або Upload повністю скинути поточний Explorer.

Для користувача це означає втратити контекст, у якому він щойно працював.

Стан, який не повинен випадково зникати
Explorer state: current folderbreadcrumbssort modesearch queryloaded pageselected mediaactive filters

Якщо нова папка створена в поточному рівні й увімкнений режим Newest, вона повинна опинитися у правильній позиції без хаотичного переходу користувача до іншої папки або скидання breadcrumbs.

Newest folders і Recent folders - це не одне й те саме

Newest можна визначити без додаткової історії користувача: це просто папки з найновішим created_at.

Recent у значенні "останні відкриті мною папки" вже потребує окремого відстеження дій користувача.

Я не хочу вводити нову систему user history лише заради швидкого доступу, якщо поточну проблему вже вирішують нові папки першими й пошук.

Virtual date grouping краще за фізичні папки 2026/09/article-name

Ще одна ідея для дуже великих колекцій - показувати папки в групах за роком і місяцем.

Але це не означає, що потрібно фізично перебудувати Media hierarchy.

Віртуальна presentation grouping
2026  September    article-a    article-b    article-c   August    article-d    article-e 2025  December    ...

Таке групування може існувати тільки на рівні представлення.

Воно не повинно автоматично:

  • створювати фізичні папки Year/Month;
  • переміщувати наявні Media folders;
  • міняти storage paths;
  • ламати існуючі relations;
  • переписувати breadcrumbs реальної структури.
Date grouping є додатковим шаром навігації, а не новою доменною структурою. Його можна додавати тільки тоді, коли воно справді покращує роботу з великою колекцією.

Laravel query для папок має масштабуватися разом із pagination

Коли список переходить від десятків до тисяч sibling folders, важливо дивитися не тільки на Vue-компонент.

Запит до бази також має відповідати новому контракту: фільтрація за батьківською папкою, сортування й пошук повинні працювати до pagination.

PHP - Фільтрація і сортування перед pagination
// Simplified contract. $query    ->where('parent_id', $currentFolderId)    ->when(        $search !== '',        fn ($query) => $query            ->where('name', 'like', '%'.$search.'%')    )    ->orderByDesc('created_at')    ->paginate();
Це спрощений приклад запиту, а не твердження про точний клас або SQL поточної реалізації.

parent_id, created_at і name мають значення для продуктивності

Після переходу до серверного пошуку та pagination потрібно окремо перевірити execution plan і індекси, які підтримують реальні запити.

Особливо важливі поля, що постійно беруть участь у вибірці sibling folders та сортуванні, наприклад parent_id і created_at.

Пошук за name теж потрібно оцінювати відповідно до фактичного способу пошуку й можливостей бази.

Media Explorer pagination не повинна створювати N+1

Пагінація зменшує кількість рядків у відповіді, але сама по собі не гарантує швидкий endpoint.

Якщо для кожної папки окремо завантажуються додаткові relations або metadata, 25 записів можуть перетворитися на десятки SQL-запитів.

Тому я перевіряю не тільки кількість повернутих елементів, а й кількість запитів, потрібних для формування сторінки.

Один контракт має працювати у standalone Media і Media Picker

Media Explorer використовується не тільки як окрема сторінка бібліотеки.

Той самий провідник відкривається як reusable picker у формах Posts, Categories, News, Tips та інших місцях.

Тому пошук, сортування, pagination і стан поточної папки не повинні мати дві різні реалізації.

Одна поведінка в обох режимах
Standalone Media page ----+                           |                           +--> shared Media Explorer contract                           |Media Picker --------------+

Мобільний Media Explorer не може залежати від hover

При великій колекції мобільна версія особливо чутлива до невдалого інтерфейсу.

Пошук і сортування мають залишатися доступними без hover або right-click, а scroll area не повинна містити сотні чи тисячі одночасно відрендерених рядків.

Breadcrumbs також мають залишатися доступними після пошуку й довантаження.

Пошук, сортування і pagination не повинні скидати selected media

Для Media Picker є ще один стан, якого немає у звичайного списку папок: вибраний файл.

Якщо користувач змінив sort mode або довантажив наступну сторінку, вже вибране Media не повинно випадково скидатися лише через оновлення колекції.

Як тестувати Laravel Media Explorer з великою кількістю папок

Сценарій

Очікування

Default load

Нові папки зверху

Oldest

Старі папки зверху

Name A-Z

Природний порядок назв

Name Z-A

Природний зворотний порядок

Пошук

Знаходить папку поза першою завантаженою сторінкою

Довантаження

Немає дублів і пропусків

Parent/child

Не змішуються sibling folders різних рівнів

Create Folder

Нова папка займає правильне місце в Newest

Upload

Поточна папка й breadcrumbs залишаються правильними

Media Picker

Selected media не скидається випадково

Mobile

Search і sort доступні без hover

Тест із 1000 папок перевіряє не тільки backend

Навіть якщо SQL-запит повертає результат швидко, інтерфейс можна зламати, якщо потім відрендерити тисячу рядків одразу.

Тому для великої колекції я перевіряю весь шлях:

  • скільки рядків читає база;
  • скільки записів повертає API;
  • скільки елементів рендерить Vue;
  • чи може користувач знайти папку без ручного прокручування;
  • чи зберігається стан після наступного запиту.

Що я не роблю при масштабуванні Media Explorer

  • Не завантажую всі 1000+ папок лише для клієнтського фільтрування.
  • Не роблю нескінченне прокручування єдиним способом знайти потрібну папку.
  • Не змінюю file sorting лише тому, що змінила folder sorting.
  • Не ламаю природне сортування числових назв у Name mode.
  • Не дублюю sort logic окремо для list, grid і picker.
  • Не скидаю current folder та breadcrumbs після Create або Upload без причини.
  • Не створюю фізичну структуру Year/Month лише заради візуального групування.
  • Не завантажую весь folder tree наперед.
  • Не додаю user history subsystem лише для простого quick access.
  • Не вважаю pagination достатньою без перевірки N+1 та DOM size.

Checklist для Laravel Media Explorer на сотні й тисячі папок

  • За замовчуванням показувати найновіші папки першими.
  • Мати окремий пошук папок.
  • Не вимагати повного завантаження колекції для пошуку.
  • Додати debounce та clear.
  • Підтримувати Newest, Oldest, Name A-Z і Name Z-A.
  • Зберігати природне числове сортування в Name mode.
  • Використовувати один shared sort contract.
  • Пагінувати sibling folders.
  • Довантажувати наступні сторінки без дублів і пропусків.
  • Завантажувати children тільки поточної папки.
  • Не скидати breadcrumbs, filters і selected media.
  • Перевірити query indexes та N+1.
  • Не рендерити весь великий набір у DOM.
  • Перевірити standalone page і reusable picker.
  • На мобільному не залежати від hover або right-click.
  • Якщо потрібне date grouping, робити його віртуальним.

Коли папок стає багато, проблема не в тому, що scroll недостатньо довгий. Проблема в тому, що користувач узагалі не повинен шукати потрібну папку прокручуванням сотень рядків.

Висновок: Media Explorer має масштабувати навігацію, а не довжину списку

Коли в Media Explorer ICanUp було кілька папок, простий список був достатнім. Коли їх стало близько 60, уже стало видно, що той самий підхід не переживе зростання до сотень або тисяч.

Першим практичним покращенням стало сортування нових папок за created_at DESC. Але цього недостатньо для великої бібліотеки.

Тому масштабований Explorer потребує пошуку без завантаження всієї колекції, передбачуваних режимів сортування, пагінації sibling folders, збереження breadcrumbs і selection та однакового контракту для standalone Media і reusable picker.

Природне сортування назв залишається окремою відповідальністю Name mode, а можливе групування за роком і місяцем не повинно змінювати реальну Media hierarchy.

Мій головний висновок: коли колекція росте, потрібно оптимізувати не прокручування. Потрібно зробити так, щоб користувачеві більше не доводилося прокручувати весь список.