Performanța aplicațiilor web: principii pe care nu le predă nimeni
Timpi de încărcare, bundle sizes, Core Web Vitals — sunt indicatori, nu obiective. Înțelegerea cauzelor reale ale performanței slabe și a modului corect de a le măsura este jumătate din bătălie.
Cel mai mare mit al optimizării de performanță
Migrezi pe un CDN mai rapid, reduci dimensiunea imaginilor și adaugi lazy loading. Site-ul tot e lent. Ce s-a întâmplat?
Probabil ai rezolvat problemele vizibile, nu problemele reale. Optimizarea fără măsurare precisă este ghicire mascată în inginerie.
Nivelele performanței: o ierarhie pe care trebuie să o înțelegi
Performanța are straturi. O optimizare la nivel greșit nu ajută (sau face lucrurile mai rele):
1. Algoritmul — O complexitate O(n²) nu devine O(n) adăugând un cache.
2. Baza de date — Un query fără index care face table scan pe 10 milioane de rânduri nu se rezolvă cu Redis în față.
3. Arhitectura de rețea — N+1 query-uri cu 200ms latency fiecare = 20 secunde pentru 100 items.
4. Randarea — Bundle de 3MB care blochează main thread-ul înainte de a arăta ceva.
5. Caching — Ineficient, dar abia la final. Nu caching-ul salvează o arhitectură greșită.
Cum să măsori corect
Instrumentele gratuite (Lighthouse, WebPageTest) sunt excelente, dar au limite: măsoară experiența sintetică, nu pe utilizatorul real.
Ce să adaugi obligatoriu:
- Real User Monitoring (RUM): Google CrUX sau un tool ca Datadog RUM pentru a vedea p75/p95 pe utilizatorii reali
- Database query profiling: EXPLAIN ANALYZE în PostgreSQL, slow query log în MySQL
- Backend tracing: OpenTelemetry + Jaeger/Zipkin pentru a vedea unde se pierde timpul în request-uri
- Frontend profiling: React Profiler pentru componente, Chrome DevTools Performance tab pentru runtime
Cazul clasic: N+1 queries
Cel mai frecvent bottleneck în aplicații cu ORM:
GET /api/orders
→ SELECT * FROM orders (1 query, 200ms)
→ FOR EACH order: SELECT * FROM users WHERE id = ? (100 queries × 50ms = 5 secunde)
Total: 5.2 secunde pentru 100 comenzi
Soluția: eager loading sau un JOIN corect reduce asta la 250ms.
Lessonul: niciun cache nu ajută dacă nu găsești N+1 mai întâi.
Core Web Vitals: ce contează cu adevărat
- LCP (Largest Contentful Paint): Optimizezi imaginea hero sau fontul care blochează render-ul
- INP (Interaction to Next Paint): Identifici handlere de click care fac muncă sincronă pe main thread
- CLS (Cumulative Layout Shift): Rezervi spațiu explicit pentru elemente care se încarcă async
Nu optimiza LCP dacă INP este 800ms — utilizatorul simte interactivitatea mai mult decât viteza de încărcare.
Priorități greșite frecvente
❌ Minifici CSS când problema e un query de 3 secunde ❌ Adaugi CDN când problema e logică de business sincronă pe server ❌ Optimizezi frontend când problema e API-ul care face 20 de calluri externe
Concluzie
Performanța bună este rezultatul deciziilor de arhitectură corecte luate de la început, nu al patch-urilor aplicate la final. Cele mai eficiente optimizări le facem la nivelul logicii și al bazei de date — nu la nivel de compilator CSS.
Dacă ai o aplicație lentă și nu știi de unde să începi, un audit de performanță de 1-2 săptămâni este cel mai scurt drum spre claritate.