Optymalizacja PLP - list produktów w sklepie B2B z 50k+ SKU
Page Listing produktów to najczęściej najwolniejsza i jednocześnie najważniejsza strona sklepu B2B. Klient przegląda PLP więcej niż jakąkolwiek inną stronę - filtruje, sortuje, paginuje. Każda sekunda tutaj boli. Klasyczny problem: sklep z 50k SKU, PLP wymaga 4-8 sekund na filtrowanie po 5 atrybutach. Po optymalizacji - 300-600 ms.
Spis treści (9)
W skrócie
- 1. Search dedykowany (Elasticsearch / Algolia) - nie ma optymalizacji PLP w MySQL przy 10k+ SKU
-
2.
Cache cen kontraktowych w Redis (klucz
(klient, sku), TTL 5-15 min) - 3. Lazy loading obrazów z placeholderami (CLS-friendly)
- 4. Pagination + virtual scroll zamiast „pokaż wszystkie 1000"
- 5. CDN dla thumbnaili (image optimization on-the-fly)
Bottleneck'i typowej PLP
PLP w Magento 2.4 dla 50k SKU z 5 filtrami atrybutów:
| Etap | Czas typowo (bez optymalizacji) |
|---|---|
| Server response (TTFB) | 600-2000 ms |
| HTML parse + critical CSS | 200-500 ms |
| Filtrowanie + agregacje (MySQL) | 2000-8000 ms |
| Cena kontraktowa per produkt (× 50) | 5000-15000 ms |
| Obrazy thumbnaili (× 50) | 1000-4000 ms |
| Render | 200-500 ms |
| Total LCP | 5-15 sekund |
Po optymalizacji:
| Etap | Czas |
|---|---|
| Server response | 100-300 ms (cache hit Varnish) |
| Filtrowanie (Elastic) | 100-400 ms |
| Cena (Redis cache hit) | 50-150 ms (batch) |
| Obrazy (CDN edge cache, WebP) | 100-300 ms |
| Render | 100-300 ms |
| Total LCP | 1.5-2.5 sekundy |
70-85% redukcja. Realne.
Filtrowanie - search engine, nie MySQL
Główne narzędzie. Przy 10k+ SKU i 3+ filtrach MySQL po prostu nie wystarczy.
Magento 2.4+ wymaga Elasticsearch lub OpenSearch. Out-of-the-box przyspieszenie filtrowania, ale wymaga konfiguracji indeksu (mappings, analyzers).
Co warto zrobić poza defaults:
- Custom analyzers dla języka polskiego (stemming „kupić" = „kupowanie" = „kupić")
- Faceted search aggregations w jednym query (zamiast 20 queries per filter)
- Custom ranking dla B2B (top-sellery dla danego klienta, dostępność w preferowanym magazynie)
- Reindex strategy - delta zamiast full (zmiana produktu = reindex tylko jego, nie całości)
Search dedykowany - pełna analiza.
Cena kontraktowa - batch + cache
W B2B największa pułapka. 50 produktów × wywołanie silnika cen ERP per produkt = 50 wywołań × 100-300 ms = 5-15s.
Strategia:
1. Batch wywołanie. Zamiast 50 wywołań „daj cenę dla produktu X" - jedno wywołanie „daj ceny dla klienta Y na liście [sku1, sku2, ..., sku50]". ERP zwraca mapę. Czas: 200-500ms.
2. Cache w Redis. Klucz (klient, sku), TTL 5-15 min. Hit ratio target >85%.
3. Cache invalidation:
- TTL wygasa
- Webhook z ERP przy zmianie cennika
- Manual flush z panelu admina
Praktyczna implementacja w Magento (przykład):
// Zamiast pojedynczych wywołań:
foreach ($products as $product) {
$price = $erp->getPrice($customer->getId(), $product->getSku()); // 50× ERP call!
}
// Batch + cache:
$skus = array_map(fn($p) => $p->getSku(), $products);
$cached = $this->priceCache->getMulti($customer->getId(), $skus);
$missing = array_diff($skus, array_keys($cached));
if (!empty($missing)) {
$fetched = $erp->getPricesBatch($customer->getId(), $missing); // 1 ERP call
$this->priceCache->setMulti($customer->getId(), $fetched, 600); // cache 10 min
$cached = array_merge($cached, $fetched);
}
Efekt: 5-15s → 50-200ms (cache hit) lub 200-500ms (cache miss).
Stany magazynowe - podobnie
Też batch + cache:
- Klucz:
stock:{sku} - TTL: 30-60s (krótsze niż ceny, stany zmieniają się częściej)
- Pull on-demand dla konkretnego produktu, gdy klient go ogląda
W Magento: warto użyć custom inventory provider z cache + batch logic.
Thumbnaile produktów - obrazy
50 thumbnaili po 200 KB = 10 MB. Klient ma 50 KB/s download? Nie kupi.
Optymalizacja:
1. WebP / AVIF zamiast JPG. -70% wagi.
2. Responsywne rozmiary. Klient na mobile widzi 200×200 px, klient na desktopie 400×400. Dwa różne obrazy.
<img
srcset="
/img/produkt-200.webp 200w,
/img/produkt-400.webp 400w,
/img/produkt-800.webp 800w
"
sizes="(max-width: 768px) 200px, 400px"
src="/img/produkt-400.webp"
width="400"
height="400"
alt="..."
loading="lazy"
>
3. Lazy loading dla above-the-fold. Pierwsze 4-6 obrazów eager (widoczne od razu), reszta loading="lazy".
4. CDN z image optimization. URL image.jpg?w=400&format=webp → CDN konwertuje on-the-fly.
Pagination, infinite scroll, virtual scroll
Klasyk: „pokaż wszystkie 1000 produktów na jednej stronie".
Tragedia performance. 1000 produktów × wszystkie operacje = LCP 30+ sekund, OOM browser na mobile.
Najlepsze podejścia:
1. Pagination klasyczna (24-48 produktów / strona).
- Plus: prosta, SEO friendly
- Plus: kontrola wagi strony
- Minus: klient klika „następna" - full reload
2. Infinite scroll.
- Plus: UX gładkie
- Minus: SEO problematyczne (Google nie scrolluje), accessibility issues
- Minus: nie da się wrócić do pozycji w liście
3. Load More button.
- Plus: kompromis między pagination a infinite
- Plus: prosty, accessible
- Standard w nowoczesnym B2B
4. Virtual scroll (advanced).
- Plus: 10000 produktów może żyć w DOM bez problemów
- Minus: wymaga frameworka (React, Vue) i custom kodu
- Use case: bardzo specyficzne (np. lista do quick-order z 1000 pozycji)
Magento default: pagination 12-24 produktów. Dla B2B często warto zwiększyć do 48 (kupujący scrolluje listę szybko, ale 1000 to za dużo).
Filtry - UX a performance
Powiązane. Każdy filtr potrzebuje:
- Lista opcji (z licznikami)
- Aktualizacja layoutu po zaznaczeniu
UX patterns:
1. Liczniki w filtrach (faceted). „Producent: Bosch (123), Makita (87)..." - wartość dla klienta, koszt - agregacje na backend.
2. Bulk apply. Klient zaznacza kilka filtrów, klika „Zastosuj". Zamiast każde zaznaczenie = osobny request.
3. Sticky filters mobile. Drawer / panel z filtrami, nie scrollable side panel jak na desktop.
4. Active filters as chips. Klient widzi co zaznaczył, łatwo zdjąć.
5. Smart defaults. „Tylko dostępne" zaznaczone domyślnie. Daje sensowne wyniki na start.
Mobile PLP - specyfika
B2B częściej na desktopie, ale mobile użytkownik to często „szybko sprawdź". Optymalizacje mobile:
1. Smaller initial set. Mobile ładuje 12 produktów, desktop 24.
2. Less filters above the fold. Mobile pokazuje tylko 2-3 najważniejsze filtry, reszta w drawer.
3. Larger touch targets. Filtry, sortowanie, paginacja - palec, nie kursor.
4. Sticky add-to-cart. Mobile często zapomina o produktach. Sticky CTA „Dodaj do koszyka" widoczne.
Najczęstsze błędy
1. Brak Elastica. Próby optymalizacji MySQL przy 50k+ SKU - strata czasu.
2. Brak cache cen. 50 wywołań ERP per render - natychmiastowy zabójca.
3. Brak lazy loading thumbnaili. Wszystkie 50 obrazów pobierane na load.
4. Reindex pełen przy każdej zmianie. 50k SKU × pełen reindex Elastic = 30 minut blokady.
5. „Pokaż wszystkie produkty". 1000+ produktów / strona = OOM na mobile, LCP 30s.
6. Filtry przeładowujące całą stronę. Każde zaznaczenie filtra = full page reload. Powinno być AJAX (zmiana URL + replace fragment).
FAQ
Czy Algolia jest lepsze niż Elasticsearch dla PLP? Dla małych i średnich katalogów (do ~20k SKU) - Algolia ma lepsze UX out-of-the-box (instant search, typeahead, custom ranking). Powyżej tej skali cena Algolia zaczyna kąsać.
Czy Magento ma dobre AJAX layered navigation? Adobe Commerce - tak (Live Search). Magento OS - moduły komercyjne (Amasty, Mageworx) lub custom. Hyvä Theme ma własną implementację, bardzo lekką.
Co z filtrami po cenie kontraktowej? Trudne. Cena per klient = nie da się zindeksować dla wszystkich. Strategia: filtrowanie tylko po cenie katalogowej + show real (kontraktowej) na karcie produktu. Lub przedział cenowy w filtrze (np. „do 100 zł", „100-500 zł") z konwencją „cena katalogowa".
Czy pre-rendering PLP statycznie (SSG) ma sens? Dla niezalogowanych - tak, agresywny full-page cache (Varnish). Dla zalogowanych z cenami kontraktowymi - nie (różny content per user).
Co z performance dla bardzo długiej listy kategorii (1000+ kategorii)? Hierarchiczna struktura, lazy load drzewa (rozwiniecie po kliknięciu). Mega menu z cache.
Co dalej
- Core Web Vitals (pełne): Core Web Vitals
- Search w architekturze: Search Algolia / Elastic
- Cache cen kontraktowych: Cache warstwy
- Lazy loading: Lazy loading obrazów
- Pillar wydajności: Wydajność e-commerce
O autorze
Jakub Owsianka
Architekt rozwiązania w WiseB2B - silniku platform B2B. Zaczynał po stronie biznesu (własne sklepy), potem deweloper, dziś projektuje wdrożenia dla sklepów z katalogami w dziesiątkach tysięcy SKU. W ostatnich latach wdrożył AI-development w zespole i funkcjonalności oparte o AI bezpośrednio w silniku sklepu.
Masz pytanie do tego artykułu?
Dodatkowy kontekst, problem z własnym wdrożeniem, druga opinia - napisz wprost. Odpowiadam osobiście w 1-2 dni robocze.