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

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: нові папки знаходяться зверху без додаткової взаємодії.
// Simplified query contract. $folders = $query ->orderByDesc('created_at') ->orderByDesc('id');Другий критерій потрібен для стабільності, якщо кілька рядків мають однаковий час створення.
Конкретна реалізація tie-breaker може відрізнятися, але результат не повинен хаотично міняти порядок між запитами.
Сортування папок: Newest, Oldest, Name A-Z і Name Z-A
Режим | Контракт |
|---|---|
Newest |
|
Oldest |
|
Name A-Z | Природне сортування назв за зростанням |
Name Z-A | Природне сортування назв у зворотному порядку |
Важливо, щоб цей вибір не існував окремо в кожному списку або grid-компоненті.
Media Explorer має один спільний контракт сортування, а поточний режим зберігається в стані сторінки або поточної сесії Explorer.
Natural sorting числових назв має залишитися в режимі Name
До масштабування списку я вже окремо виправляла випадок, коли цифрові назви сортувалися як 1, 10, 11, 2.
Тому додавання режимів Newest і Oldest не повинно повертати стару проблему. У режимі Name зберігається природне сортування числових назв.
Name A-Z: 123101120 not: 11011220Laravel folder search має працювати без завантаження всіх 1000 папок
Коли колекція велика, пошук тільки на клієнтській частині вирішує не ту проблему.
Щоб відфільтрувати 1000 папок у браузері, спочатку довелося б отримати всі 1000 папок через API.
Тому пошук має виконуватися на сервері або іншим способом, який не вимагає передавати всю колекцію клієнту.
Wrong: API -> load every folder | vfrontend filters 1000+ rows Scalable: query | vserver filters folders | vDebounce і Clear потрібні навіть для простого folder search
Я не хочу надсилати окремий HTTP-запит на кожен символ під час швидкого введення.
Тому поле пошуку має debounce, а очищення запиту повинно повертати користувача до звичайного стану списку без повного перезавантаження Media Explorer.
Результат пошуку має відкрити папку і не втратити breadcrumbs
Знайти назву недостатньо.
Після вибору результату користувач має потрапити всередину папки так само, ніби він пройшов до неї звичайною навігацією.
Поточна папка та ланцюжок навігації повинні залишитися узгодженими.
folder search | vmatching folder | vopen folder | +--> current folder updated +--> breadcrumbs updated +--> children loadedPagination і lazy loading замість одного великого folder response
Кореневий рівень Media Explorer більше не повинен означати "завантажити все, що існує".
Клієнт отримує тільки потрібну сторінку sibling folders, а наступні записи довантажуються за потреби.
root folders page 1 | vrender current folders | vneed more? | +--> request next page | vappend without replacing previous entriesLoad more має append, а не замінювати вже завантажені записи
Це особливо важливо, бо в Media Explorer вже був реальний клас помилки, де наступна сторінка успішно приходила з API, але дані не додавалися до попереднього списку.
Для папок контракт такий самий: попередні записи залишаються, нова сторінка додається, дублікати не з’являються, а metadata визначає, чи є ще дані.
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);Відкриття папки має завантажувати тільки потрібних 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.
2026 September article-a article-b article-c August article-d article-e 2025 December ...Таке групування може існувати тільки на рівні представлення.
Воно не повинно автоматично:
- створювати фізичні папки Year/Month;
- переміщувати наявні Media folders;
- міняти storage paths;
- ламати існуючі relations;
- переписувати breadcrumbs реальної структури.
Laravel query для папок має масштабуватися разом із pagination
Коли список переходить від десятків до тисяч sibling folders, важливо дивитися не тільки на Vue-компонент.
Запит до бази також має відповідати новому контракту: фільтрація за батьківською папкою, сортування й пошук повинні працювати до pagination.
// Simplified contract. $query ->where('parent_id', $currentFolderId) ->when( $search !== '', fn ($query) => $query ->where('name', 'like', '%'.$search.'%') ) ->orderByDesc('created_at') ->paginate();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.
Мій головний висновок: коли колекція росте, потрібно оптимізувати не прокручування. Потрібно зробити так, щоб користувачеві більше не доводилося прокручувати весь список.



