De clientbundle analyseren en verkleinen
Gebruik de bundle analyzer om zware afhankelijkheden te vinden en de naar gebruikers gestuurde client-JavaScript te beperken.
De clientbundle analyseren en verkleinen is een gratis Fullstackontwikkeling met Next.js 15 (App Router + Server Actions)-les op CoddyKit. Dit is les 1 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Fullstackontwikkeling met Next.js 15 (App Router + Server Actions). Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) bevat in totaal 4 lessen.
Waarom de grootte van de clientbundel belangrijk is
Elke kilobyte JavaScript die je app naar gebruikers verstuurt, heeft een prijs: downloadtijd, verwerkingstijd en uitvoeringstijd. Op een mobiel apparaat uit het middensegment met een 4G-verbinding kan een JS-bundel van 1 MB 3–5 seconden blokkerende tijd toevoegen voordat de pagina interactief wordt.
Next.js 15 met de App Router doet al veel voor je:
- Server Components worden nooit naar de client gestuurd
- Op routes gebaseerde codesplitsing gebeurt automatisch
- Tree shaking verwijdert ongebruikte exports
Maar bibliotheken van derden, onbedoelde clientimports en grote hulppakketten kunnen je bundel ongemerkt groter maken. In deze les leer je hoe je die problemen systematisch vindt en oplost.
@next/bundle-analyzer installeren
De officiële bundelanalyser van Next.js wikkelt webpack-bundle-analyzer in en integreert netjes met next.config.ts. Installeer deze één keer als ontwikkelafhankelijkheid:
Na de installatie wikkel je je Next.js-configuratie met de analyzerfabriek. Deze leest de omgevingsvariabele ANALYZE, zodat het rapport alleen wordt geopend wanneer je daar expliciet om vraagt — je normale builds blijven ongewijzigd.
De analyser genereert twee interactieve HTML-treemapdiagrammen:
- client.html — JavaScript dat naar de browser wordt gestuurd
- server.html — Node.js-serverbundel (nuttig, maar van ondergeschikt belang)
// Terminal
// npm install --save-dev @next/bundle-analyzer
// next.config.ts
import type { NextConfig } from 'next';
import bundleAnalyzer from '@next/bundle-analyzer';
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
openAnalyzer: true,
});
const nextConfig: NextConfig = {
// your existing config
};
export default withBundleAnalyzer(nextConfig);Een analysebuild uitvoeren
Voeg een handig script toe aan package.json, zodat de analyseopdracht makkelijk te onthouden is en in je hele team hetzelfde blijft:
Wanneer je npm run analyze uitvoert, maakt Next.js een volledige productiebuild en opent daarna twee browsertabbladen met de treemaprapporten. De grootte van elke rechthoek staat voor de verwerkte (ongecomprimeerde) grootte in bytes van die module in je bundel.
Let in de clienttreemap vooral op het volgende:
- Onverwacht grote rechthoeken afkomstig van één bibliotheek
- Bibliotheken die je alleen op de server gebruikt, maar die in het clientdeel verschijnen
- Meerdere kopieën van dezelfde bibliotheek (versieconflicten)
// package.json (relevant section)
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"analyze": "ANALYZE=true next build"
}
}Het treemaprapport lezen
De treemap groepeert modules per deelbundel. Elke productiepagina in je App Router-app heeft doorgaans minstens drie typen deelbundels:
- app/layout-deelbundel — gedeeld door alle routes; alles wat je in je hoofdlayout importeert, komt hierin terecht
- pagin specifieke deelbundels — code die uniek is voor één route
- gedeelde deelbundels — modules die door twee of meer pagina's worden gebruikt en automatisch door webpack worden geëxtraheerd
Beweeg de muis over een rechthoek om het volledige modulepad en de stat-grootte (ruw), verwerkte grootte (na minificatie) en gzip-grootte (wat over het netwerk wordt verzonden) te zien. Optimaliseer altijd voor de gzip-grootte — dat zijn de echte kosten voor de gebruiker.
Een veelvoorkomende bevinding: een datumbibliotheek zoals moment of date-fns waarbij alle landinstellingen zijn gebundeld en die 200–500 KB verwerkt in de gedeelde deelbundel inneemt.
Het probleem van onbedoelde clientimports
In de App Router zijn componenten standaard Server Components. Zodra een Server Component echter een module importeert die ergens in zijn afhankelijkheidsboom een 'use client'-grens heeft — of je per ongeluk een serverhulpmiddel in een Client Component importeert — gaat er iets mis.
Een vaker voorkomend probleem is het omgekeerde: een zware bibliotheek importeren in een 'use client'-component terwijl die ook op de server zou kunnen staan. Een Markdown-renderer, syntaxmarkeerder of PDF-parser hoeft bijvoorbeeld niet in de browser te draaien als deze alleen statische inhoud weergeeft.
De oplossing is eenvoudig: verplaats de renderlogica naar een Server Component en geef het resultaat door als prop of slot.
// BEFORE: heavy library shipped to the client
'use client';
import { marked } from 'marked'; // ~50 KB parsed
export function BlogPost({ raw }: { raw: string }) {
return <div dangerouslySetInnerHTML={{ __html: marked(raw) }} />;
}
// AFTER: render on the server, ship only HTML
// app/blog/[slug]/page.tsx (Server Component — no 'use client')
import { marked } from 'marked';
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const raw = await fetchPostMarkdown(slug);
const html = marked(raw) as string;
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
async function fetchPostMarkdown(slug: string): Promise<string> {
// fetch from DB / CMS
return `# Hello from ${slug}`;
}Dynamische imports en lui laden
Next.js exporteert next/dynamic opnieuw: een dunne wrapper rond React.lazy met ingebouwde SSR-besturing. Gebruik deze om componenten af te splitsen die:
- Pas na een gebruikersinteractie zichtbaar worden (modale vensters, zijpanelen, tooltips)
- Onder de vouw staan (grafieken, reactiesecties)
- Groot en route-specifiek zijn
De optie ssr: false is belangrijk voor bibliotheken die alleen in de browser werken (bibliotheken die toegang hebben tot window of document) — hiermee voorkom je dat de server ze überhaupt probeert te importeren.
De prop loading toont een skelet terwijl de deelbundel wordt gedownload en voorkomt verschuiving van de lay-out.
// app/dashboard/page.tsx
import dynamic from 'next/dynamic';
// Heavy chart library — only loaded when the chart is rendered
const RevenueChart = dynamic(
() => import('@/components/RevenueChart'),
{
loading: () => <p>Loading chart...</p>,
ssr: false, // recharts/d3 uses window internally
}
);
// Modal — only loaded when user clicks "Export"
const ExportModal = dynamic(
() => import('@/components/ExportModal')
);
export default function DashboardPage() {
return (
<main>
<h1>Dashboard</h1>
<RevenueChart />
<ExportModal />
</main>
);
}Tree shaking: benoemde imports versus standaardimports
Webpack en Turbopack kunnen ongebruikte code verwijderen — maar alleen wanneer bibliotheken zo zijn opgebouwd dat dit mogelijk is (ESM met benoemde exports). Ook je importstijl is belangrijk.
Een volledige bibliotheeknamespace importeren maakt tree shaking ongedaan, zelfs bij ESM-bibliotheken. Geef altijd de voorkeur aan benoemde imports voor de specifieke functie die je nodig hebt.
Bij bibliotheken die nog uitsluitend CommonJS (CJS) leveren, is tree shaking onmogelijk. Controleer de analyser: als je één hulpfunctie importeert uit een CJS-bibliotheek van 300 KB, komt de volledige 300 KB in je bundel terecht. Zoek in dat geval naar een lichter ESM-alternatief of kopieer alleen de functie die je nodig hebt.
Gebruik het veld sideEffects: false in de package.json van je eigen pakket om aan te geven dat al je modules veilig met tree shaking kunnen worden verwerkt.
// BAD — pulls in the entire lodash CJS bundle (~70 KB gzipped)
import _ from 'lodash';
const result = _.groupBy(items, 'category');
// BETTER — lodash-es is ESM; named import is tree-shakeable
import { groupBy } from 'lodash-es';
const result = groupBy(items, 'category');
// BEST for simple cases — just write it yourself
function groupBy<T>(arr: T[], key: keyof T): Record<string, T[]> {
return arr.reduce(
(acc, item) => {
const group = String(item[key]);
(acc[group] ??= []).push(item);
return acc;
},
{} as Record<string, T[]>
);
}
const result = groupBy(items, 'category');Zware bibliotheken vervangen door lichtere alternatieven
De optimalisatie met de grootste impact is vaak een grote bibliotheek vervangen door een kleinere die precies doet wat je nodig hebt. Veelvoorkomende vervangingen:
- moment (67 KB gzip) → date-fns (geschikt voor tree shaking, ongeveer 3 KB per functie) of dayjs (2 KB gzip)
- axios (13 KB gzip) → native
fetch(0 KB, ingebouwd in Node 18+ en alle browsers) - lodash (70 KB gzip) → lodash-es + tree shaking, of native arraymethoden
- highlight.js (volledig) → importeer alleen de talen die je nodig hebt via de slanke build
Het onderstaande patroon laat zien hoe je moment vervangt door date-fns in een Server Action-context waarin slechts één formatteringsfunctie nodig is.
// BEFORE — moment ships all locales by default
// import moment from 'moment';
// const label = moment(date).format('MMM D, YYYY');
// AFTER — date-fns: import exactly what you need
import { format } from 'date-fns';
// This Server Action formats a timestamp — runs only on the server
export async function getFormattedDate(
isoString: string
): Promise<string> {
'use server';
const date = new Date(isoString);
return format(date, 'MMM d, yyyy'); // e.g. "Jun 11, 2026"
}De echte bundelimpact meten met de uitvoer van next build
Je hebt niet altijd de volledige visuele treemap nodig. Next.js geeft na elke productiebuild een tabel met routegroottes weer. Leer deze te lezen:
- Size — JavaScript dat specifiek voor die route wordt gedownload (unieke route-deelbundels)
- First Load JS — alle JavaScript, inclusief gedeelde deelbundels (wat de gebruiker bij het eerste bezoek daadwerkelijk downloadt)
Onderaan staat het totaal van de gedeelde deelbundels. Een groene First Load JS is kleiner dan 100 KB. Geel is een waarschuwing. Rood (meer dan 500 KB) vereist onmiddellijk aandacht.
Houd dit getal bij in CI door de build te laten mislukken wanneer het een drempel overschrijdt, met experimental.bundlePagesRouterDependencies of een aangepast script voor groottelimieten.
// scripts/check-bundle-size.ts
// Run after `next build` parses .next/build-manifest.json
import fs from 'node:fs';
import path from 'node:path';
const FIRST_LOAD_LIMIT_KB = 200;
interface BuildManifest {
pages: Record<string, string[]>;
}
function getFileSizeKb(filePath: string): number {
try {
const stats = fs.statSync(filePath);
return stats.size / 1024;
} catch {
return 0;
}
}
const manifestPath = path.join(process.cwd(), '.next/build-manifest.json');
const manifest: BuildManifest = JSON.parse(
fs.readFileSync(manifestPath, 'utf-8')
);
let exceeded = false;
for (const [page, files] of Object.entries(manifest.pages)) {
const totalKb = files.reduce((sum, f) => {
return sum + getFileSizeKb(path.join(process.cwd(), '.next', f));
}, 0);
if (totalKb > FIRST_LOAD_LIMIT_KB) {
console.error(`FAIL ${page}: ${totalKb.toFixed(1)} KB > ${FIRST_LOAD_LIMIT_KB} KB`);
exceeded = true;
}
}
if (exceeded) process.exit(1);
console.log('Bundle size check passed.');modularizeImports gebruiken voor pictogram- en UI-bibliotheken
Pictogrambibliotheken zoals react-icons en componentbibliotheken zoals @mui/material staan erom bekend dat ze bundels enorm vergroten wanneer je ze zonder verdere configuratie importeert. Zelfs een benoemde import zoals import { FiTrash } from 'react-icons/fi' kan de volledige pictogrammenset binnenhalen als de interne structuur van de bibliotheek niet geschikt is voor tree-shaking.
Next.js 15 biedt modularizeImports in next.config.ts om barrel-imports tijdens het bouwen automatisch om te zetten in directe diepe imports — je hoeft niets aan je componentbestanden te wijzigen.
Dit is gelijkwaardig aan het schrijven van import FiTrash from 'react-icons/fi/index.js', maar gebeurt transparant, zodat je broncode overzichtelijk blijft.
// next.config.ts
import type { NextConfig } from 'next';
import bundleAnalyzer from '@next/bundle-analyzer';
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});
const nextConfig: NextConfig = {
modularizeImports: {
// Rewrites: import { FiTrash } from 'react-icons/fi'
// To: import FiTrash from 'react-icons/fi/FiTrash'
'react-icons/?((<alpha>*))': {
transform: 'react-icons/{{ matches.[1] }}/{{ member }}',
},
// Same pattern for @mui/material
'@mui/material': {
transform: '@mui/material/{{ member }}',
},
'@mui/icons-material': {
transform: '@mui/icons-material/{{ member }}',
},
},
};
export default withBundleAnalyzer(nextConfig);Serverpakketten buiten de bundel houden
Sommige pakketten mogen nooit in de clientbundel terechtkomen. Databasestuurprogramma's (pg, mysql2), versleutelingsbibliotheken (bcrypt) en hulpprogramma's voor het bestandssysteem zijn bewust uitsluitend voor de server bedoeld. Next.js 15 biedt twee mechanismen om dit af te dwingen:
- Het npm-pakket
server-only— als jeimport 'server-only'aan een module toevoegt, ontstaat er tijdens het bouwen een fout als die module ooit in een Client Component wordt geïmporteerd experimental.serverComponentsExternalPackagesinnext.config.ts— hiermee geef je de bundelaar opdracht bepaalde pakketten als Node.js-require()-aanroepen te laten staan in plaats van ze in de bundel op te nemen
Door beide samen te gebruiken krijg je extra zekerheid: de importbeveiliging vangt fouten in je code op en de externe configuratie handelt systeemeigen binaire bestanden af die webpack sowieso niet kan bundelen.
// lib/db.ts — database client, server-only
import 'server-only'; // build error if imported in a Client Component
import { Pool } from 'pg';
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
experimental: {
// These packages stay as require() — not bundled by webpack
// Needed for native addons like bcrypt, sharp, prisma engine
serverComponentsExternalPackages: ['bcrypt', 'sharp', '@prisma/client'],
},
};
export default nextConfig;Kennistoets: strategie voor bundeloptimalisatie
Je voert npm run analyze uit en ontdekt dat highlight.js (volledige build, ongeveer 900 KB geparseerd) in je clientbundel staat. De syntaxisaccentuering wordt alleen gebruikt op de berichtpagina's van /blog/[slug] om codeblokken van kleur te voorzien. De codeblokken zijn statisch — voor iedere bezoeker hetzelfde. Wat is de beste optimalisatiestrategie?
Samenvatting: de clientbundel analyseren en verkleinen
In deze les heb je een systematische aanpak geleerd om de hoeveelheid JavaScript die je Next.js 15-app naar gebruikers verstuurt te verminderen:
@next/bundle-analyzerinstalleren en uitvoeren — verpakt je build in een interactieve treemap; je activeert dit metANALYZE=true next build- De treemap lezen — richt je in het clientrapport op de gecomprimeerde grootte; let op grote rechthoeken, pakketten die alleen op de server nodig zijn en dubbele modules
- Werk dat alleen op de server nodig is naar Server Components verplaatsen — Markdown-renderers, syntaxisaccentueerders, PDF-parsers en databaseclients horen niet in de browser thuis
next/dynamicgebruiken voor uitgesteld laden — splits modals, grafieken en componenten onder de vouw af; gebruikssr: falsevoor pakketten die alleen in de browser werken- Tree-shaking correct uitvoeren — geef de voorkeur aan benoemde ESM-imports; vervang bibliotheken die alleen CJS ondersteunen door ESM-alternatieven
modularizeImportsgebruiken — importeert leden van pictogram- en UI-bibliotheken tijdens het bouwen automatisch rechtstreeks uit diepe paden- Servergrenzen afdwingen —
import 'server-only'enserverComponentsExternalPackagesvoorkomen dat servercode per ongeluk in de client terechtkomt
Voer de analyzer vóór en na iedere optimalisatie uit om het effect te controleren. Automatiseer in CI een controle van het groottebudget om regressies te voorkomen.
Leer TypeScript met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 22
- Lessen
- 88
Veelgestelde vragen
Is de les “De clientbundle analyseren en verkleinen” gratis?
Ja — je kunt hier op het web alle 3 lessen van het leerpad Fullstackontwikkeling met Next.js 15 (App Router + Server Actions), waaronder “De clientbundle analyseren en verkleinen”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) bevat in totaal 4 lessen.
Wat leer ik in “De clientbundle analyseren en verkleinen”?
Gebruik de bundle analyzer om zware afhankelijkheden te vinden en de naar gebruikers gestuurde client-JavaScript te beperken. Je oefent met Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) te beginnen?
Ervaring vooraf is niet nodig. Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 1 van 4.
Hoe lang duurt de les “De clientbundle analyseren en verkleinen”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over Fullstackontwikkeling met Next.js 15 (App Router + Server Actions)?
Ja. Elke les over Fullstackontwikkeling met Next.js 15 (App Router + Server Actions) bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- De clientbundle analyseren en verkleinen
- Turbopack- en compilerconfiguratie uitgediept
- Modulegrenzen met server-only en client-only
- Dynamische imports, code splitting en lazy hydration