Resursa

Cum optimizezi Core Web Vitals în Next.js fără să distrugi UX-ul

Ghid practic pentru LCP, CLS și INP în proiecte reale.

De ce contează

Core Web Vitals sunt semnale de calitate: viteza percepută, stabilitatea layout-ului și cât de “responsive” e site-ul la interacțiuni. În Next.js poți obține rezultate foarte bune, dar doar dacă optimizezi corect: nu doar scor Lighthouse, ci viteza reală pe mobil.

Indicatori principali:

  • LCP: cât de repede apare conținutul principal (de obicei imagine/hero/text mare)
  • CLS: cât de mult “saltă” pagina
  • INP: cât de repede răspunde site-ul la click/tap

1) Începe cu măsurarea corectă

Nu începe cu “hai să minificăm CSS”. Începe cu:

  • Lighthouse (debug local, rapid)
  • Real User Monitoring (RUM) dacă ai (cel mai bun)
  • PageSpeed Insights pentru trenduri (ok ca semnal, nu ca adevăr absolut)

Checklist minim de măsurare:

  • 3 pagini critice: Home, categorie/listare, produs/checkout
  • test pe mobil, nu doar desktop
  • notează LCP elementul (ce e exact) și sursa CLS (ce componentă saltă)

2) Fixează LCP

A) Imaginea din hero / produs

În Next.js, folosește next/image și fă imaginea above the fold prioritară.

Reguli simple:

  • set priority doar pentru imaginea principală
  • definește width/height (sau fill + container stabil)
  • folosește sizes corect pentru responsive

Exemplu:

import Image from "next/image";

export function HeroImage() {
  return (
    <div style={{ position: "relative", width: "100%", height: 420 }}>
      <Image
        src="/hero.webp"
        alt="..."
        fill
        priority
        sizes="(max-width: 768px) 100vw, 1200px"
        style={{ objectFit: "cover" }}
      />
    </div>
  );
}

B) Server response / caching

LCP moare dacă:

  • faci fetch-uri lente fără caching
  • ai logică grea în request (SSR) fără reason

Reguli:

  • cache pe datele care nu se schimbă la fiecare secundă
  • evită SSR la pagini de marketing dacă poți (SSG/ISR)
  • controlezi caching cu next: { revalidate: ... }

C) Fonturi

Fonturile mari + încărcare blocantă = LCP slab.

Reguli:

  • folosește next/font
  • evită 10 greutăți (2 sunt suficiente)

3) Fixează CLS

CLS apare când UI își schimbă mărimea după ce pagina a început să se afișeze.

Cauze clasice:

  • imagini fără dimensiuni fixe
  • bannere care apar după load și împing conținutul
  • font swap fără fallback bun
  • componente care injectează conținut (cookie banner, modale)

Fixuri rapide:

  • rezervă spațiu pentru imagini
  • cookie banner ca overlay (fără push)
  • skeletons cu dimensiuni reale

4) Fixează INP

INP scade când:

  • ai prea mult JS în bundle
  • ai handler-e grele la click/scroll
  • ai re-render-uri masive

Reguli practice:

  • dynamic import pentru componente grele
  • mută logică în Server Components unde se poate
  • evită librării grele pentru lucruri simple

Exemplu:

import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("./HeavyChart"), { ssr: false });

export function Dashboard() {
  return (
    <section>
      <h2>Raport</h2>
      <HeavyChart />
    </section>
  );
}

Optimizări React simple:

  • folosește useMemo/useCallback doar când chiar previi re-render
  • evită state global care re-render‑uiește tot
  • debouncing la input‑uri care fac query/search

5) Quick wins care aduc rezultate reale

  • Hero image: priority + sizes + format webp/avif
  • Reduce font weights la 2
  • Elimină scripturi inutile (analytics multiple, chat, heatmaps)
  • Code split pentru componente rare (carusele, hărți)
  • Cache pentru date stabile (ISR / revalidate)

Checklist final

LCP

  • elementul LCP identificat
  • imaginea principală are priority + sizes
  • fonturi optimizate (next/font)
  • caching pentru fetch‑uri lente

CLS

  • imagini cu dimensiuni rezervate
  • bannere fără push (overlay sau spațiu rezervat)
  • componente dinamice cu skeleton stabil

INP

  • bundle redus (dynamic import)
  • re-render‑uri limitate
  • handler‑e optimizate (debounce, fără heavy work pe main thread)