Skip to content

Pagina de produs își desenează deschiderea înainte de JavaScript

Vitalie requested to merge 44_product-first-paint into main

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.

Merge request reports