Kritische CSS splitsen in Tailwind v3/v4 op Laravel + Vite
Als een pagina traag aanvoelt, staren gebruikers naar een leeg scherm terwijl CSS het renderen blokkeert. Een pragmatische oplossing: splits de critical CSS voor de above-the-fold weergave eruit en stel de rest uit. Deze handleiding laat een schone setup met twee ingangen zien in Laravel + Vite, met productieklare voorbeelden voor Tailwind v3 en v4, waarom PageSpeed/Core Web Vitals belangrijk is en wat je moet vermijden.
Waarom PageSpeed belangrijk is (LCP/CLS)
Kritische CSS is de minimale set stijlen die nodig is om het eerste scherm (above-the-fold) te renderen. Door dit deel vroeg aan te leveren (inline of als een klein bestand) en de resterende CSS uit te stellen, verminder je renderblokkerend werk en verbeter je LCP. Het zorgvuldig instellen van lettertypen kan ook onverwachte verschuivingen in de lay-out verminderen en CLS helpen. Bekijk voor een breder perspectief op snelheid versus stabiliteit Snel zonder de gevolgen.
Basisstrategie (Laravel + Vite)
- Twee ingangspunten:
critical.cssvoor het eerste scherm;app.cssvoor al het andere (plugins, uitgebreide hulpprogramma's, typografie in lange vorm). - Laadvolgorde: lever het belangrijkste op in
<head>(inline of een normale link). Stel de rest uit metrel="preload" as="style"+onloaden een<noscript>fallback. - Vermijd duplicaten: houd basis/componenten in kritisch en verplaats het grootste deel van de hulpprogramma's naar de uitgestelde bundel; zorg ervoor dat de bronbestanden die door elke build worden gescand elkaar niet overlappen.
Tailwind v3: twee configuraties + een preset
1) Deel je thema één keer
// tailwind.shared.js
module.exports = {
theme: {
extend: {
// colors, spacing, etc.
},
},
plugins: [],
};
2) Kritische build (scan alleen sjablonen boven de vouw)
// tailwind.critical.config.js
module.exports = {
presets: [require('./tailwind.shared')],
content: [
'./resources/views/layouts/app.blade.php',
'./resources/views/partials/header.blade.php',
'./resources/views/home/hero.blade.php',
],
};
/* resources/css/critical.css */
@config "tailwind.critical.config.js";
@tailwind base;
@tailwind components;
@tailwind utilities;
3) App bundel (sluit bestanden uit die door kritisch worden gebruikt)
// tailwind.app.config.js
module.exports = {
presets: [require('./tailwind.shared')],
content: [
'./resources/**/*.blade.php',
'./resources/**/*.vue',
'!./resources/views/partials/header.blade.php',
'!./resources/views/home/hero.blade.php',
],
};
/* resources/css/app.css */
/* Avoid duplicating base/components (already shipped in critical) */
@config "tailwind.app.config.js";
@tailwind utilities;
Als je dynamische klassenamen hebt, voeg ze dan toe aan safelist in beide configuraties.
4) Bouwen en verbinden via Vite (Laravel)
// vite.config.ts
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/critical.css',
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
],
})
<!-- Blade: load critical normally (or inline it) -->
@vite('resources/css/critical.css')
<!-- Defer the rest: preload + onload + noscript -->
@php $appCss = Vite::asset('resources/css/app.css'); @endphp
<link rel="preload" as="style" href="{{ $appCss }}" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ $appCss }}"></noscript>
Tailwind v4: CSS-first sources met fijnmazige controle
In Tailwind v4 is de configuratie CSS-first. In plaats van alles van Tailwind in één keer te importeren, importeer je alleen de modules die elke bundel nodig heeft — zo voorkom je dat de reset (preflight) in beide bestanden wordt gedupliceerd. Declareer sources expliciet met @source, gebruik afzonderlijke layer-namen om de cascade-volgorde over bundels heen te vergrendelen, en registreer plugins met @plugin.
1) Gedeelde tokens
/* resources/css/theme.css */
@theme {
/* brand tokens, breakpoints, etc. */
--color-brand-500: oklch(0.72 0.12 250);
--breakpoint-2xl: 96rem;
}
2) Kritische CSS: scan alleen wat nodig is
/* resources/css/critical.css */
@import "./theme.css";
/* Enumerate just the above-the-fold templates */
@source "../views/layouts/app.blade.php";
@source "../views/partials/header.blade.php";
@source "../views/home/hero.blade.php";
/* Declare ALL layers upfront — including "deferred" from the app bundle.
This locks cascade order regardless of async load timing. */
@layer theme, base, deferred, utilities;
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);
3) App CSS: al het andere (sluit uit wat kritisch al omvat)
/* resources/css/app.css */
@import "./theme.css";
/* Scan Blade templates and JS (Alpine components, dynamic classes) */
@source "../views/**/*.blade.php";
@source "../js/**/*.js";
/* ...except templates already handled by critical */
@source not "../views/layouts/app.blade.php";
@source not "../views/partials/header.blade.php";
@source not "../views/home/hero.blade.php";
/* Use a dedicated layer so deferred utilities never override critical ones */
@layer deferred;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(deferred);
/* v4 plugin syntax (replaces the plugins array in config) */
@plugin "@tailwindcss/typography";
/* Inline safelist if you generate classes dynamically */
@source inline("md:grid lg:gap-6 bg-brand-500");
4) Vite setup en Blade laden
// vite.config.ts (same as v3)
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/critical.css',
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
],
})
<!-- Blade: same pattern as v3 -->
@vite('resources/css/critical.css')
@php $appCss = Vite::asset('resources/css/app.css'); @endphp
<link rel="preload" as="style" href="{{ $appCss }}" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ $appCss }}"></noscript>
Wat hoort in kritisch (en wat niet)
- Opnemen: basisinstellingen, header/nav-structuur, heldensectie, essentiële knoppen/links, minimale typografie op het eerste scherm.
- Vermijd: zware onderdelen onder de vouw, langwerpige
.prosetypografie, zeldzame widgets.
Lettertypen & CLS-minirecepten
Lay-out verschuivingen treden vaak op wanneer het fallback lettertype wordt vervangen door het weblettertype. Gebruik size-adjust en gerelateerde metric overrides om beter overeen te komen met het fallback lettertype en laad alleen de echt kritieke vlakken vooraf.
@font-face {
font-family: "InterVar";
src: url("/fonts/InterVar.woff2") format("woff2");
font-weight: 100 900;
font-display: swap;
/* Tweak metrics to reduce shifts */
size-adjust: 100%;
ascent-override: 92%;
descent-override: 24%;
line-gap-override: 0%;
}
Controleer het resultaat
- Voer PageSpeed/Lighthouse uit en vergelijk LCP; controleer "Verminder render-blocking bronnen" en "Verminder ongebruikte CSS".
- Sanity check: controleer of er geen duplicatie is tussen bundels; als je extra gewicht ziet, bekijk dan de gescande bronnen opnieuw.
- Onthoud: labscores zijn indicatief; let op de algemene trend en veldgegevens. Voor meer over het versnellen van een Laravel-site buiten CSS om, die gids behandelt caching, database en asset-pipelines.
Veelvoorkomende valkuilen
- Overlappende bronnen → duplicaten: in v3, repareer je
contentglobs; in v4, gebruik expliciete@sourceen@source not. - Dezelfde layer-naam in beide bundels: als zowel critical als app
@layer utilitiesdeclareren, hangt de cascade-volgorde af van welk bestand het laatst laadt. Geef de app-bundel zijn eigen layer (bijv.deferred) en declareer deze vooraf in de@layer-lijst van critical om de volgorde te vergrendelen. - Plugins/typografie in kritisch: dit maakt het bestand gemakkelijk voller — houd het in
app.css. In v4, gebruik@pluginom ze te registreren. - Inlining kritisch vs CSP: als je CSP streng is, verzend kritisch dan als een klein extern bestand.
- migratie v3 → v4: verplaats tokens van
theme.extendnaar@theme, schakel vancontentnaar@source, vervang@tailwind basedoor@import "tailwindcss/preflight.css", en geef de voorkeur aan CSS-first imports.
Alternatieven als twee-entry niet jouw ding is
- Automatisch gegenereerde kritieke (bijv. critical, critters): het snelst om te beginnen; moeilijker om te debuggen op complexe sjablonen.
- Enkele configuratie + meerdere bronnen: houd één config (v3) of CSS-first setup (v4) en beheer inclusie/exclusie via globs/
@source. - Route-level CSS: bundels per pagina alleen geladen waar nodig; krachtig maar voegt infra complexiteit toe.
- Component-scoped kritisch: inline kleine stijlen voor belangrijke boven-de-vouw componenten; discipline vereist, werkt goed voor herhaalde blokken.
Samenvatting
Met een kleine kritische bundel in <head> en een uitgestelde hoofdbundel geef je de browser minder om op te blokkeren en gebruikers een snellere eerste paint. Tailwind v3 geeft de voorkeur aan twee configs en zorgvuldige content paden; Tailwind v4 maakt dit nog netter met CSS-first @source controle. Op Laravel + Vite is het een eenvoudige, onderhoudbare opzet die de waargenomen snelheid meetbaar verbetert.
Hoe groot mag de kritische CSS-bundel zijn?
Minder dan 14 KB gecomprimeerd is een goed doel — dat past in de eerste TCP-roundtrip (circa 14 KB na TLS-overhead). Op deze site is de inline kritische CSS ongeveer 6 KB gzip.
Werkt deze aanpak met Tailwind JIT?
Ja. JIT genereert alleen de klassen die daadwerkelijk worden gebruikt in gescande bestanden, wat van nature aansluit bij het scannen van specifieke templates per bundel. In Tailwind v4 is JIT de enige modus — er is geen klassieke engine meer.
Moet ik kritische CSS inline plaatsen of een link-tag gebruiken?
Inline is het snelst — nul extra requests. Maar het conflicteert met streng CSP-beleid dat inline stijlen blokkeert. Een klein extern bestand met rel="preload" is het praktische midden als CSP een punt van aandacht is.
Hoe controleer ik of er geen duplicatie is tussen bundels?
Open DevTools → tabblad Coverage, laad de pagina en controleer hoeveel CSS in elk bestand ongebruikt blijft. Als beide bundels dezelfde regels bevatten, zie je hoge ongebruikte percentages in een ervan. Vergelijk ook de ruwe bestandsgroottes voor en na het splitsen.
Neem contact op
Een projectin gedachten?
Deel je context en gewenst resultaat. Binnen 1 werkdag sturen we de eenvoudigste volgende stap (tijdlijn, ruwe begroting of snelle audit).