First Contentful Paint (FCP): Stop het lege scherm dat vertrouwen vernietigt
De gebruiker klikt op je link. Wit scherm. Eén seconde. Nog steeds wit. Twee seconden. Ze beginnen te twijfelen of ze wel op het juiste ding hebben geklikt. Drie seconden. Ze drukken op terug. Je hebt nooit de kans gekregen om ook maar iets te laten zien.

Wat is FCP (en waarom lege schermen conversies doden)?
FCP meet wanneer de browser de eerste pixel aan content rendert. Tekst, afbeeldingen of een achtergrondkleur die niet wit is. Vóór FCP is je pagina niet te onderscheiden van een kapotte pagina.
Gebruikers oordelen razendsnel: Als ze twee seconden niets zien, gaan ze ervan uit dat de pagina niet heeft geladen, dat hun internet hapert of dat ze op het verkeerde ding hebben geklikt. Ze vertrekken. Zelfs als je volledige LCP maar 2,5 seconden is, ondermijnt dat lege scherm aan het begin het vertrouwen.
FCP-normen
FCP vs LCP: FCP meet wanneer er überhaupt iets gerenderd wordt. LCP meet wanneer de hoofdinhoud gerenderd wordt. Je wil beide snel hebben, maar LCP is het Core Web Vital.
Welke content telt voor FCP?
Counts for FCP:
✓ Text (even if web font hasn't loaded)
✓ Images (including background images)
✓ SVG elements
✓ Canvas elements
✓ Non-white background colors on elements
Does NOT count:
✗ Iframes
✗ White canvas/SVG (no visible content)
✗ Loading spinners (usually)Waarom FCP belangrijk is voor de gebruikerservaring
FCP gaat over waargenomen snelheid. Gebruikers beslissen binnen de eerste 1–2 seconden of ze blijven of vertrekken. Iets snel laten zien houdt ze betrokken terwijl de rest laadt.
Eerste indruk
Een leeg scherm gedurende 3 seconden voelt als een eeuwigheid. Gebruikers gaan ervan uit dat de pagina kapot is en vertrekken. Een snelle FCP geeft het signaal: "deze website werkt."
Invloed op bouncepercentage
Elke seconde vertraging in FCP verhoogt het bouncepercentage met 8–12%. Mobiele gebruikers op trage netwerken zijn bijzonder gevoelig voor de duur van het lege scherm.
Mobiele prestaties
Mobiele apparaten hebben langzamere processors. Parse- en rendertijd telt zwaarder. FCP optimaliseren helpt mobiele gebruikers onevenredig veel.
FCP terugbrengen van 3 s naar 1,5 s verlaagt het bouncepercentage met 15–20%. Gebruikers zijn geduldig als ze voortgang zien.
FCP meten
Velddata (echte gebruikers)
FCP varieert per apparaat, netwerk en locatie. Velddata toont de echte gebruikerservaring:
- Iron/Out gratis benchmark — haal je FCP op uit het Chrome UX Report
- PageSpeed Insights — toont veld-FCP-data uit het Chrome UX Report
- Real User Monitoring — volg FCP voor elke bezoeker met onze observability-implementatie
Labdata (testen)
Test FCP in gecontroleerde omgevingen:
- Lighthouse — rapporteert FCP met impact op de performancescore
- PageSpeed Insights — toont zowel lab- als veld-FCP
- WebPageTest — visuele filmstrip toont exact wanneer FCP plaatsvindt
FCP visualiseren in Chrome DevTools
// Chrome DevTools Performance tab:
// 1. Open DevTools (F12)
// 2. Go to Performance tab
// 3. Click Record, reload page, stop recording
// 4. Look for "FCP" marker in the timeline
// 5. Screenshot view shows first paint moment
// The filmstrip view shows:
// - Blank frames before FCP
// - First visible content at FCP
// - Progressive content loadingFCP programmatisch meten
Installeer de web-vitals library:
npm install web-vitalsTrack vervolgens FCP:
import {onFCP} from 'web-vitals';
onFCP((metric) => {
console.log('FCP:', metric.value);
// Send to analytics
analytics.track('fcp', {
value: metric.value,
rating: metric.rating, // 'good', 'needs-improvement', 'poor'
delta: metric.delta,
navigationType: metric.navigationType
});
});
// Or use Paint Timing API directly
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime);
}
}
});
observer.observe({ type: 'paint', buffered: true });Veelvoorkomende oorzaken van trage FCP
1. Render-blocking CSS
Browsers renderen pas nadat CSS is geladen en geparsed. Grote stylesheets of traag ladende externe CSS vertragen FCP aanzienlijk.
Fix: Inline kritieke CSS, stel niet-kritieke stijlen uit, minimaliseer de grootte van stylesheets.
2. Render-blocking JavaScript
JavaScript in de <head> blokkeert het parsen en renderen van HTML. Elk blokkerend script voegt 50–200 ms toe aan FCP.
Fix: Gebruik async/defer-attributen, verplaats scripts naar het einde van de body, inline kritieke JS.
3. Trage serverrespons (TTFB)
Er wordt pas iets gerenderd als de HTML binnenkomt. Een hoge TTFB telt direct op bij FCP. Een TTFB van 2 seconden betekent dat FCP nooit beter kan zijn dan 2 seconden.
Fix: Optimaliseer serverprestaties, gebruik een CDN, implementeer caching. Zie de TTFB-gids.
4. Grote DOM-opbouw
De browser moet HTML parsen en de DOM opbouwen voordat hij kan renderen. Complexe HTML met duizenden elementen vertraagt dit proces.
Fix: Vereenvoudig de HTML-structuur, laad content buiten het scherm lazy, server-side render alleen kritieke content.
5. Vertragingen bij het laden van weblettertypen
Met font-display: block wordt tekst pas gerenderd als lettertypen zijn geladen. Dit zorgt voor "onzichtbare tekst" en vertraagt FCP.
Fix: Gebruik font-display: swap of optional, preload kritieke lettertypen, gebruik systeemlettertypen als fallback.
FCP-optimalisatietechnieken
FCP optimaliseren draait grotendeels om je <head>-sectie. Alles daarin (CSS-links, scripts, font preloads) blokkeert de browser bij het renderen van de eerste pixel. Maak de head opgeruimd en FCP verbetert.
1. Elimineer render-blocking resources
JavaScript uitstellen
<!-- Bad: Blocks HTML parsing -->
<script src="/bundle.js"></script>
<!-- Good: Defer execution -->
<script src="/bundle.js" defer></script>
<!-- Also good: Async loading (order not guaranteed) -->
<script src="/analytics.js" async></script>
<!-- Modern approach: Type module (deferred by default) -->
<script type="module" src="/app.js"></script>
Defer vs Async:
- defer: Execute in order after HTML parsing
- async: Execute immediately when loaded (random order)
- type="module": Always deferred, supports ES6 imports2. Serverresponstijd optimaliseren (TTFB)
Snelle TTFB maakt snelle FCP mogelijk
Een snelle TTFB is essentieel voor een snelle FCP. Zie onze uitgebreide TTFB-gids voor:
- CDN edge caching
- Server-side cachingstrategieën
- Databasequeryoptimalisatie
- Edge rendering voor wereldwijde prestaties
Doel: TTFB onder 800 ms maakt FCP onder 1,8 s mogelijk
3. Kritieke resources preloaden
<!-- Preload the most critical font -->
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
<!-- Preload hero image -->
<link rel="preload" href="/hero.jpg" as="image">
<!-- Preconnect to critical third-party origins -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Priority hints for important images -->
<img src="/hero.jpg" alt="Hero" fetchpriority="high">
Warning: Don't preload too many resources
- Limit to 2-3 critical resources
- Over-preloading delays everything4. Het laden van weblettertypen optimaliseren
font-display gebruiken voor directe tekstrendering
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
/* Show fallback immediately, swap when loaded */
font-display: swap;
}
/* Or prevent font-based layout shift */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
/* Only use font if it loads in ~100ms */
font-display: optional;
}
font-display options:
- swap: Show fallback, swap when loaded (best for FCP)
- optional: Use font only if cached (prevents layout shift)
- block: Wait for font (delays FCP, avoid this)
- fallback: 100ms block, 3s swap periodLettertypen subsetten en variable fonts gebruiken
<!-- Bad: Load entire font family -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap">
<!-- Good: Load only what you need -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap&text=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789">
<!-- Better: Use variable font (single file, all weights) -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap">
File size comparison:
- Multiple weights (4 files): 200-400KB
- Variable font (1 file): 80-150KB
- Subset variable font: 40-80KB5. Minimaliseer main thread-werk tijdens het initieel laden
// Bad: Heavy parsing/execution blocks FCP
import { createApp } from 'vue';
import router from './router';
import store from './store';
import components from './components'; // 200+ components
import './styles.css';
createApp(App)
.use(router)
.use(store)
.use(components)
.mount('#app');
// Good: Lazy load non-critical code
import { createApp } from 'vue';
// Minimal initial bundle
const app = createApp(App);
// Load router/store/components after FCP
requestIdleCallback(() => {
import('./router').then(({ default: router }) => {
app.use(router);
});
import('./store').then(({ default: store }) => {
app.use(store);
});
});
app.mount('#app');
Result:
- Initial bundle: 200KB -> 50KB
- Parse time: 400ms -> 80ms
- FCP improvement: 300-500ms6. Kritieke content server-side renderen
// Next.js: Server-side render for instant FCP
export async function getServerSideProps() {
const data = await fetchCriticalData();
return { props: { data } };
}
export default function Page({ data }) {
return (
<div>
{/* This HTML is sent from server, renders immediately */}
<h1>{data.title}</h1>
<p>{data.description}</p>
</div>
);
}
// Client-side rendering (CSR):
// 1. Load HTML (100ms)
// 2. Load JS (300ms)
// 3. Execute JS (200ms)
// 4. Fetch data (150ms)
// 5. Render (50ms)
// = 800ms to FCP
// Server-side rendering (SSR):
// 1. Server renders HTML (150ms)
// 2. Send HTML (50ms)
// 3. Browser renders (50ms)
// = 250ms to FCPGeavanceerde FCP-optimalisatiestrategieën
Streaming SSR voor instant FCP
// React 18: Stream HTML as it renders
import { renderToPipeableStream } from 'react-dom/server';
app.get('/', (req, res) => {
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
// Send initial HTML immediately
res.setHeader('Content-Type', 'text/html');
pipe(res);
// Browser starts rendering before full page loads
}
});
});
// Benefits:
// - FCP happens instantly (shell renders first)
// - Content streams in progressively
// - Users see something in 50-100msResource hints voor sneller laden
<!-- Early hints (HTTP 103) for instant resource loading -->
Link: </critical.css>; rel=preload; as=style
Link: </hero.jpg>; rel=preload; as=image
<!-- Implemented in Cloudflare Workers / Fastly -->
export default {
async fetch(request) {
return new Response(html, {
status: 200,
headers: {
'Link': '</critical.css>; rel=preload; as=style'
}
});
}
}
// Browser receives hints before HTML, starts loading immediately
// Can save 100-300ms on critical resourcesProgressive rendering-technieken
<!-- Show skeleton/placeholder immediately -->
<div class="page">
<div class="skeleton-header"></div>
<div class="skeleton-content"></div>
</div>
<style>
/* Inline skeleton styles for instant FCP */
.skeleton-header {
width: 100%;
height: 60px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: -200px 0; }
100% { background-position: 200px 0; }
}
</style>
<!-- Load actual content via JS -->
<script defer src="/app.js"></script>
Result:
- FCP: 200ms (skeleton renders)
- Meaningful paint: 800ms (content loaded)
- User sees progress immediatelyScripts van derden optimaliseren
<!-- Bad: Third-party scripts block FCP -->
<script src="https://www.googletagmanager.com/gtag/js"></script>
<script src="https://connect.facebook.net/en_US/sdk.js"></script>
<!-- Good: Defer all third-party scripts -->
<script src="https://www.googletagmanager.com/gtag/js" async></script>
<script src="https://connect.facebook.net/en_US/sdk.js" defer></script>
<!-- Better: Lazy load after FCP -->
<script>
// Wait for page to be interactive
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
loadAnalytics();
loadChatWidget();
});
} else {
setTimeout(() => {
loadAnalytics();
loadChatWidget();
}, 2000);
}
</script>FCP monitoren over tijd
FCP kan verslechteren naarmate je features toevoegt. Monitor het om regressies vroeg te signaleren.
FCP tracken met gedetailleerde context
import {onFCP} from 'web-vitals';
onFCP((metric) => {
analytics.track('fcp', {
value: metric.value,
rating: metric.rating,
page_path: location.pathname,
navigation_type: metric.navigationType,
connection_type: navigator.connection?.effectiveType,
device_memory: navigator.deviceMemory,
// Breakdown: Time to first byte
ttfb: getTTFB(),
// Detect render-blocking resources
blocking_resources: getBlockingResources()
});
});
function getBlockingResources() {
const resources = performance.getEntriesByType('resource');
return resources
.filter(r => r.renderBlockingStatus === 'blocking')
.map(r => ({ url: r.name, duration: r.duration }));
}Alerts instellen
- P75 FCP-norm: Alert als het 75e percentiel boven 1,8 s uitkomt
- Mobiel vs. desktop: Volg apart; mobiele FCP is doorgaans 2–3 keer langzamer
- Regressiedetectie: Alert bij een FCP-stijging van meer dan 200 ms na een deployment
- Correlatieanalyse: Volg de relatie tussen TTFB en FCP
Performance budgets
- FCP P75: Onder 1,8 s wereldwijd
- TTFB: Onder 800 ms (maakt snelle FCP mogelijk)
- Blocking resources: Maximaal 2 render-blocking CSS-bestanden
- CSS-bundel: Totale CSS onder 100 KB houden
- Above-fold afbeeldingen: Preload slechts 1–2 kritieke afbeeldingen
FCP-optimalisatiechecklist
Hulp nodig bij het verbeteren van je FCP?
Het optimaliseren van het critical rendering path vraagt om diepgaande technische kennis. Wij kunnen je resources auditeren, blokkeringen elimineren en bewezen oplossingen implementeren. Neem contact op of voer een gratis benchmark uit om je huidige FCP te zien.
Hulp nodig bij het verbeteren van je FCP?
Wij optimaliseren je critical rendering path en elimineren render-blocking resources.