Ga naar inhoud
·3 min

Performance optimalisatie in Next.js: van 4s naar 0.8s

Hoe ik de Largest Contentful Paint van een productie-app van 4 seconden naar onder de 1 seconde kreeg. Concrete stappen en metingen.

PerformanceNext.jsWeb VitalsTutorial

Vorige maand deed ik een performance audit op een Next.js productie-app. De Largest Contentful Paint (LCP) was 4.1 seconden op mobiel. Na een week optimalisatie: 0.8 seconden. Hier zijn de exacte stappen die ik heb genomen.

LCP verbetering per optimalisatiestap — elke stap levert meetbaar resultaat
0%
LCP reductie
0%
Kleinere JS bundle
0%
CLS verbetering
0%
TTFB reductie
De totale performance verbetering na 5 optimalisatiestappen
🖼️

Stap 1: Images optimaliseren

next/image met priority, sizes en WebP. Van 2.4MB naar 180KB. Impact: -0.9s LCP.

🔤

Stap 2: Fonts lokaal hosten

next/font elimineert externe requests. font-display: swap voorkomt FOIT. Impact: -0.6s.

📦

Stap 3: JS bundle verkleinen

Tree-shaking, selectieve imports, verwijder ongebruikte libraries. Van 485KB naar 220KB. Impact: -0.8s.

💾

Stap 4: Server-side caching

unstable_cache voor statische data, dynamisch voor user-specifiek. TTFB van 1.2s naar 0.4s. Impact: -0.6s.

Stap 5: Streaming met Suspense

Suspense boundaries voor progressieve rendering. Gebruiker ziet direct content. Impact: -0.4s.

De 5 optimalisatiestappen die LCP van 4.1s naar 0.8s brachten — klik door elke stap

De uitgangssituatie

De app was een dashboard met ~15 pagina's, gebouwd met Next.js 16 en Tailwind CSS. De Core Web Vitals waren:

MetricWaardeBeoordeling
LCP4.1sSlecht
FID180msMatig
CLS0.24Slecht
TTFB1.2sMatig

Meten voor je optimaliseert

Optimaliseer nooit op basis van aannames. Meet eerst met Lighthouse, WebPageTest of de Chrome DevTools Performance tab. Mijn eerste instinct was "de bundel is te groot" — maar het bleek dat images het grootste probleem waren.

Stap 1: Image optimalisatie (-0.9s)

De homepage had een hero image van 2.4MB als PNG. De eerste fix was de makkelijkste:

// ❌ Voorheen
<img src="/hero.png" alt="Dashboard preview" />
 
// ✅ Na optimalisatie
<Image
  src="/hero.png"
  alt="Dashboard preview"
  width={1200}
  height={630}
  priority
  sizes="(max-width: 768px) 100vw, 80vw"
/>

De priority prop voorkomt lazy loading voor above-the-fold images. De sizes prop zorgt ervoor dat Next.js de juiste afbeeldingsgrootte stuurt per viewport.

Resultaat: 2.4MB → 180KB WebP. LCP van 4.1s naar 3.2s.

Stap 2: Font optimalisatie (-0.6s)

De app laadde drie fonts via Google Fonts — elk met een render-blocking request. De fix:

// next.config.ts
import { Inter, DM_Serif_Display, JetBrains_Mono } from 'next/font/google';
 
const inter = Inter({ subsets: ['latin'], display: 'swap' });
const dmSerif = DM_Serif_Display({ weight: '400', subsets: ['latin'], display: 'swap' });

next/font host de fonts lokaal, elimineert de DNS lookup naar Google, en voegt font-display: swap toe.

Resultaat: 3 externe requests geëlimineerd. LCP naar 2.6s.

Stap 3: JavaScript bundle verkleinen (-0.8s)

Met @next/bundle-analyzer ontdekte ik twee problemen:

  1. Chart.js werd volledig geïmporteerd (210KB) terwijl ik alleen bar charts gebruikte
  2. date-fns importeerde de hele library (75KB) in plaats van individuele functies
// ❌ Import alles
import { format, parseISO, differenceInDays } from 'date-fns';
 
// ✅ Tree-shakeable imports
import format from 'date-fns/format';
import parseISO from 'date-fns/parseISO';

En voor Chart.js: registreer alleen de modules die je gebruikt (zie mijn BlogChart component).

Bundle size reductie per library — lodash en moment volledig vervangen

Resultaat: Totale JS bundle van 485KB naar 220KB. LCP naar 1.8s.

Stap 4: Server-side caching (-0.6s)

De pagina deed 4 database queries bij elk request. Twee daarvan veranderden zelden:

// Layout data cached voor 1 uur
const navigation = await unstable_cache(
  () => getNavigationItems(),
  ['nav-items'],
  { revalidate: 3600 }
)();
 
// User-specifieke data altijd vers
const dashboardData = await getDashboardData(userId);

Resultaat: TTFB van 1.2s naar 0.4s. LCP naar 1.2s.

Stap 5: Streaming met Suspense (-0.4s)

De dashboard pagina wachtte tot alle data geladen was voordat er iets werd getoond. Met Suspense boundaries stream je de snelle content eerst:

export default function Dashboard() {
  return (
    <div>
      {/* Snel: uit cache */}
      <DashboardHeader />
 
      {/* Langzaam: database query */}
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart />
      </Suspense>
 
      <Suspense fallback={<TableSkeleton />}>
        <RecentOrders />
      </Suspense>
    </div>
  );
}

Resultaat: LCP naar 0.8s. De gebruiker ziet direct content terwijl charts laden.

Eindresultaat

De vijf stappen die samen 80% reductie in LCP opleverden
MetricVoorNaVerbetering
LCP4.1s0.8s-80%
FID180ms45ms-75%
CLS0.240.02-92%
TTFB1.2s0.4s-67%
JS Bundle485KB220KB-55%

Les geleerd

De grootste winsten kwamen van de simpelste optimalisaties: images en fonts. Begin daar altijd. JavaScript bundle optimalisatie en streaming zijn belangrijk, maar leveren pas resultaat nadat de basis op orde is.

Voor optimalisatie

  • LCP: 4.1 seconden
  • JS Bundle: 485KB
  • Geen image optimalisatie
  • 3 externe font requests
  • Geen caching strategie
VS

Na optimalisatie

  • LCP: 0.8 seconden
  • JS Bundle: 220KB
  • WebP met responsive sizes
  • Lokale fonts met swap
  • Agressieve server-side cache
Het verschil in 5 concrete optimalisatiestappen
Techniek
ImpactAanbevolen
Moeite
Prioriteit
next/image optimalisatieImagesHoog (-0.9s)Laag (30 min)1 - Begin hier
next/font lokaalFontsGemiddeld (-0.6s)Laag (15 min)2 - Quick win
Tree-shaking & importsJS BundleHoog (-0.8s)Gemiddeld (2-4 uur)3 - Analyse nodig
unstable_cacheTTFBHoog (-0.6s)Gemiddeld (1-2 uur)4 - Data-afhankelijk
Suspense streamingPerceived perfGemiddeld (-0.4s)Hoog (4+ uur)5 - Architectureel

Prioriteer optimalisaties op basis van impact en moeite — begin altijd met images en fonts

Bronnen

De tools die ik gebruikte:

  • Lighthouse voor de initiële audit
  • @next/bundle-analyzer voor bundle analyse
  • WebPageTest voor real-world metingen op verschillende verbindingen
  • Chrome DevTools Performance tab voor waterfall analyse

Blijf op de hoogte

Ontvang nieuwe artikelen direct in je inbox. Geen spam, alleen waardevolle content.

Gerelateerde artikelen

Meer over dit onderwerp

Plan een gesprek