Pagina de produs își desenează deschiderea înainte de JavaScript
Prima pagină își desena deja deschiderea din document; pagina de produs nu. Acum o desenează și ea — dar nu doar ca markup, ci și ca date, fiindcă doar markup-ul ar fi fost mai rău decât nimic: React îl ștergea la prima randare și nu-l punea înapoi până răspundea API-ul, adică poză, pagină albă, poză. Un pâlpâit e mai rău decât o primă vopsire târzie.
Ce face
Rendererul scrie în document două lucruri: deschiderea desenată în #root
(rezerva de antet, numele, prețul, nota și poza) și un bloc de date de 168 de
octeți în cap, cu șapte câmpuri. Nu răspunsul întreg: acela are mediana 6.752
de octeți și ajunge la 103.982 pentru normatec-3-legs, produsul cu 347 de
recenzii.
useProductDetail citește blocul sincron, la prima randare, nu dintr-un efect —
și doar dacă mânerul din document e cel din adresă, altfel un click către alt
produs ar deschide pagina pe poza celui dinainte.
Ruta de produs are acum limita ei de Suspense, a cărei stare de așteptare
desenează aceeași deschidere. Fără ea, cadrul pe care React îl comite primul
e placeholderul gol al aplicației, iar toată munca de mai sus se vedea ștearsă
timp de ~280 ms.
key={handle} pe rută rezolvă și un defect vechi: slide-ul activ din galerie,
varianta selectată și starea acordeonului supraviețuiau navigării către alt
produs.
Rezerva de antet a trecut de la 104 la 106 px — atât măsoară antetul real sub
lg (64 px rândul + 42 px banda de categorii).
Ce am verificat eu, pe pagină randată
Documentul randat de ramură servit pe același port cu build-ul ei, cu o sondă
care înregistrează fiecare mutație a lui #root și fiecare layout-shift:
| 375 px | 430 px (normatec-3-legs) |
|
|---|---|---|
cadre în care #root e gol |
0 | 0 |
| cadre fără poza produsului | 0 | 0 |
| deplasări înregistrate | niciuna | niciuna |
| poza în cadrul serverului | y=198 | y=198 |
| poza în pagina încărcată | y=198 | y=198 |
Pe main, aceeași sondă arată #root gol până pornește aplicația.
Două lucruri spuse pe față
Prima pagină pâlpâie la fel și a pâlpâit dintotdeauna. Premisa de la care a
pornit lucrarea — că prima pagină nu are cadrul gol fiindcă primește datele — e
falsă la nivel de cadru: și ea comite un cadru alb de ~286 ms. bannerData n-a
adresat niciodată asta. Se rezolvă la fel, cu o stare de așteptare în formă de
hero; primește lucrare separată.
Fontul de marcă poate re-așeza titlul pe 3 din 45 de nume. Cu font-display: swap, dacă fontul ajunge după prima vopsire, titlul trece de la două rânduri la
unul și poza urcă 24 px (layout-shift 0,0237 pe arm-attachment). Măsurat pe
profilul mobil de Lighthouse, fonturile ajung la 754 și 1.017 ms față de o primă
vopsire la 2.192 ms, deci în condițiile care contează nu se întâmplă. Singurul
mod de a-l elimina cu totul e să nu mai desenăm titlul — ceea ce urcă poza cu
76 px și costă ~0,054 pe fiecare pagină de produs.
Verificare rulată
backend/: npm run build curat, npm test — 46 de fișiere, 690 de teste.
frontend/: npm run lint, npm run build, npx vitest run — 38 de fișiere,
479 de teste. Toate trec, după rebazarea pe main.
Cifrele din criteriul de acceptare (FCP sub 1,5 s, LCP sub 2,5 s, CLS sub 0,1, mediana a 5 rulări) se măsoară pe producție după deploy. Până atunci issue-ul rămâne deschis.