Joomla template performance: optimalisatie voor ontwikkelaars
Twee Joomla-sites kunnen op dezelfde server draaien, met dezelfde PHP-versie, dezelfde caching en dezelfde extensies, en toch voelt de ene twee keer zo snel aan. Het verschil zit meestal in de template. De server bepaalt hoe snel de HTML wordt verzonden; de template bepaalt hoeveel werk de browser daarna moet doen: hoeveel CSS er moet worden geparseerd, hoeveel JavaScript er moet worden uitgevoerd, welke lettertypen er moeten worden opgehaald en hoeveel DOM-knooppunten er moeten worden opgemaakt. Dat werk aan de browserzijde is waar het grootste deel van de waargenomen snelheid zit.
Dit artikel is de verdieping voor ontwikkelaars naast het algemene Joomla-prestatie-artikel: het richt zich op de templatelaag. Het legt uit hoe Joomla een pagina samenstelt, hoe de Web Asset Manager beslist wat er laadt, en hoe je markup, CSS, JavaScript, lettertypen en afbeeldingen optimaliseert in je template en overrides. Sitebouwers krijgen praktische recepten; ontwikkelaars zien precies waar in de Joomla-code elk mechanisme leeft.
De server verstuurt de pagina één keer. De browser "betaalt" (prestatiekosten) bij elk bezoek voor je template.
Het doel is simpel: je helpen een Joomla-template te bouwen of af te stellen die snel rendert op echte apparaten, en niet alleen op je ontwikkelmachine.
1. De basis
1.1 Waar templateprestaties zitten
Het laden van een pagina heeft twee helften. Eerst de serverhelft: Joomla draait PHP, bevraagt de database en produceert HTML. Dat bepaalt de Time To First Byte (TTFB). Dan de browserhelft: de browser parset de HTML, downloadt de CSS, JavaScript, lettertypen en afbeeldingen waar de template naar verwijst, bouwt de layout op en tekent het scherm. Dat bepaalt First Contentful Paint, Largest Contentful Paint en hoe "vlot" de pagina aanvoelt.
De template domineert die tweede helft vrijwel volledig. Een template bepaalt:
- Hoeveel CSS- en JavaScript-bestanden er laden, en hoe groot ze zijn.
- Of lettertypen van je eigen server komen, van Google, of van het systeem van de bezoeker zelf.
- Hoeveel DOM-nodes de browser moet opbouwen en positioneren.
- Of afbeeldingen in de layout (logo, banners) direct of lui laden.
- Of assets op elke pagina laden of alleen waar ze gebruikt worden.
1.2 Denken in budgetten
Zie elke pagina als een budget: kilobytes om te downloaden, milliseconden om te renderen. Elke stylesheet, elk script, elk lettertype en elke wrapper-<div> geeft van dat budget uit. Een snelle template is geen template met slimme trucs; het is een template die weinig uitgeeft. Cassiopeia, de standaardtemplate van Joomla, is een goed studieobject: de eigen JavaScript is minder dan 1 KB geminificeerd, en lettertypen en kleuren zijn optionele extra's in plaats van standaardkosten.
1.3 Meet eerst
Meet voordat je iets verandert. Gebruik Lighthouse (in Chrome DevTools) of PageSpeed Insights voor de browserhelft, en Joomla's plugin Systeem - Foutopsporing voor de serverhelft. Is de TTFB traag, dan is de template niet je probleem - begin dan bij het algemene prestatie-artikel. Is de TTFB prima maar verschijnt de pagina toch laat, lees dan verder: de templatelaag is waar je gaat winnen.
Naar boven2. Onder de motorkap: hoe Joomla een pagina opbouwt
Om de templatelaag te optimaliseren helpt het om de exacte volgorde te kennen waarin Joomla een pagina opbouwt. De pijplijn is kort, maar niet vanzelfsprekend.
2.1 De pijplijn
Request
↓
SiteApplication::dispatch()
↓ het COMPONENT draait eerst; de HTML wordt in een buffer bewaard
↓ ($document->setBuffer($contents, ['type' => 'component']))
↓
de index.php van je template draait
↓ registreert assets, bouwt het paginaskelet,
↓ laat <jdoc:include /> placeholders achter in de markup
↓
HtmlDocument::parse() verzamelt alle <jdoc:include> tags
↓
HtmlDocument render: elke tag wordt vervangen door zijn renderer
component → de eerder bewaarde buffer
modules → worden NU gerenderd, positie voor positie
head → wordt als LAATSTE samengesteld uit alles wat geregistreerd is
↓
HTML-response
Uit deze volgorde volgen drie praktische conclusies:
- Het component draait voor je templatebestand. Tegen de tijd dat
index.phpdraait, bestaat de hoofdinhoud al. Je template kan het component niet sneller maken - maar bepaalt wel alles eromheen. - Modules renderen op het moment van vervangen. Elke
<jdoc:include type="modules" name="..." />voert echte modulecode uit wanneer de placeholder vervangen wordt. Elke gevulde positie kost PHP-tijd en voegt markup toe. - De head wordt als laatste samengesteld. Ook al staat
<jdoc:include type="head" />bovenaan je bestand, Joomla vult hem pas aan het einde van het renderen. Daarom kan een component, module of override een stylesheet registreren op een moment dat "te laat" voelt - en werkt het toch.
2.2 Wat de browser daarna doet
De HTML-response is het einde van Joomla's werk, maar pas het midden van het laden van de pagina. De browser draait zijn eigen pijplijn, en elke templatebeslissing landt ergens daarop:
HTML → DOM parse de markup naar een boom van nodes
CSS → CSSOM parse alle stylesheets (renderen wacht hierop)
DOM + CSSOM → render tree → layout → paint → composite
Deze trap verklaart het vocabulaire van de rest van dit artikel. "CSS blokkeert het renderen" betekent dat de browser niet kan tekenen voordat de CSSOM compleet is - en daarom doen omvang en aantal van je stylesheets ertoe. Synchrone JavaScript pauzeert het HTML-parsen op de eerste trede. Een grote DOM maakt de layoutstap duur. Elke optimalisatie in de volgende secties verkort een specifieke trede.
2.3 Waar dit in de code leeft
Het hele mechanisme zit in libraries/src/Document/HtmlDocument.php (de methoden parse() en _renderTemplate(), die de <jdoc:include> tags met een regex matchen) en de rendererklassen in libraries/src/Document/Renderer/Html/: ComponentRenderer, ModulesRenderer, ModuleRenderer, HeadRenderer, MetasRenderer, StylesRenderer, ScriptsRenderer en MessageRenderer. Deze bestanden een keer lezen ontrafelt de hele templatelaag.
3. De Web Asset Manager: laad alleen wat je nodig hebt
Sinds Joomla 4 zijn assets geen losse <link>- en <script>-tags meer, maar benoemde items in een afhankelijkheidsgraaf, beheerd door de Web Asset Manager. Dit is het belangrijkste prestatiegereedschap in de templatelaag.
3.1 Het assetregister: joomla.asset.json
Elke template en extensie kan een bestand joomla.asset.json meeleveren dat de assets declareert: een naam, een bestand, afhankelijkheden en opties. Het register van Cassiopeia (in templates/cassiopeia/joomla.asset.json) declareert onder andere:
template.cassiopeia.ltr- de hoofdstylesheet, met een afhankelijkheid vanfontawesome.template.cassiopeia- het templatescript, geladen met"type": "module"zodat het het parsen nooit blokkeert.template.user- de optioneleuser.css/user.js, met"weight": 500zodat hij altijd als laatste laadt en de cascade wint.template.active- een leeg "dummy"-asset waar extensies van kunnen afhangen om na de actieve template te laden.
Omdat afhankelijkheden gedeclareerd zijn, laadt Joomla elk asset een keer, in de juiste volgorde, hoeveel extensies er ook om vragen. Je beheert scriptvolgorde nooit meer met de hand.
3.2 Presets: een schakelaar voor een bundel
Een preset groepeert meerdere assets onder een naam. De index.php van Cassiopeia zet de hele frontend aan met een aanroep:
$wa = $this->getWebAssetManager();
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'));
De preset haalt de stylesheet en het script binnen, en hun afhankelijkheden komen automatisch mee. Let op het detail: de RTL-stylesheet laadt alleen voor talen die van rechts naar links lezen. Dat is conditioneel laden op templateniveau - de LTR-bezoeker betaalt nooit voor RTL-regels.
3.3 Conditioneel laden in overrides
De grootste winst voor echte sites: laad een asset alleen op pagina's die hem gebruiken. Een template-override is daarvoor de perfecte plek, want die draait alleen wanneer die view rendert:
<?php
// In een override, bijv. templates/cassiopeia_child/html/com_content/article/default.php
$wa = $this->getDocument()->getWebAssetManager();
$wa->registerAndUseScript(
'mysite.gallery', // assetnaam
'templates/cassiopeia_child/gallery.js', // bestand
['version' => 'auto'], // opties
['defer' => true] // attributen
);
Het galerijscript laadt nu alleen op artikelpagina's. Je homepage, contactpagina en categorielijsten zien het nooit. Dit ene patroon - paginaspecifieke assets verplaatsen van index.php naar overrides - levert vaak meer op dan welke minificatieplugin dan ook.
3.4 Cache busting met version: auto
De optie ['version' => 'auto'] plakt Joomla's mediaversie achter de URL (bijvoorbeeld template.min.css?a1b2c3). De hash wordt gegenereerd in libraries/src/Version.php uit de Joomla-versie, je secret en een datum, en verandert bij elke update en cache-leging. Zo kun je lange browsercache-tijden instellen voor statische assets zonder ooit een verouderd bestand te serveren na een update.
4. Slanke markup: DOM-gewicht en moduleposities
4.1 De DOM is niet gratis
Elk element op de pagina kost geheugen en layouttijd. Lighthouse begint te waarschuwen rond 1.500 DOM-nodes; zwaar geneste pagebuilder-output verdrievoudigt dat makkelijk. De browser moet elke node stylen, positioneren en tekenen - en JavaScript dat de DOM aanraakt wordt trager naarmate de boom groeit. Semantische, platte markup is een prestatie-eigenschap, niet alleen een kwestie van codestijl. Het helpt ook de toegankelijkheid: als ik child templates bouw voor klantsites, controleer ik WCAG-naleving en prestaties samen, want slanke semantische markup verbetert beide tegelijk.
4.2 Sla lege posities over met countModules()
Een goede template rendert nooit wrappers voor lege moduleposities. Cassiopeia controleert eerst:
<?php if ($this->countModules('sidebar-left', true)) : ?>
<div class="grid-child container-sidebar-left">
<jdoc:include type="modules" name="sidebar-left" style="card" />
</div>
<?php endif; ?>
Het tweede argument true laat de telling rekening houden met menutoewijzingen en toegangsniveaus, zodat een module die aan andere pagina's is toegewezen geen lege sidebarkolom achterlaat. Zonder deze controle krijg je spookkolommen, zwevende marges en CSS-workarounds die weer extra markup kosten.
4.3 Chrome: de wrapper om elke module
Het attribuut style="card" kiest de module-chrome - de wrapper-markup die om elke module gerenderd wordt. Joomla lost hem op via LayoutHelper::render('chromes.' . $style) in ModuleHelper, en Cassiopeia levert twee chromes in html/layouts/chromes/: card.php (een Bootstrap-card met kop) en noCard.php (minimaal). Kies je style="none", dan rendert de module-output zonder enige wrapper. Vermenigvuldig de chromekosten met het aantal modules op de pagina: tien modules met een zware chrome voegen tientallen extra nodes toe voor pure decoratie.
5. CSS-strategie
5.1 Weet wat je verstuurt
Cijfers van een standaard Joomla 6-installatie: Cassiopeia's template.min.css is ongeveer 247 KB ongecomprimeerd (hij bevat heel Bootstrap), en de fontawesome-afhankelijkheid voegt zo'n 100 KB icoon-CSS toe. Compressie maakt dit op de lijn veel kleiner - Joomla levert zelfs voorgecomprimeerde .gz-bestanden naast elk geminificeerd asset - maar de browser parset nog steeds het volledige bestand. Een maatwerktemplate die alleen de CSS bevat die hij gebruikt, kan een tiende van die omvang zijn.
5.2 Een bestand verslaat vele - meestal
Met HTTP/2 is de oude regel "combineer alles in een bestand" versoepeld, maar een stapel kleine stylesheets kost nog steeds ontdekkingstijd, en @import-ketens zijn het slechtste geval: elke import wordt pas ontdekt nadat het vorige bestand gedownload is. Houd het simpel: een gecompileerde, geminificeerde templatestylesheet, plus user.css voor sitespecifieke aanpassingen. Groeit je user.css voorbij een paar honderd regels, dan is het tijd om de regels te verhuizen naar de gecompileerde stylesheet van een child template.
5.3 Inline het kritieke, stel de rest uit
Cassiopeia inlinet zijn CSS custom properties (kleuren, lettertypen) met $wa->addInlineStyle() direct in index.php - een klein kritiek blok dat geen extra request nodig heeft. Hetzelfde idee schaalt op: inline de kleine hoeveelheid CSS die nodig is om de bovenkant van de pagina te tekenen, en laad de rest zonder te blokkeren. Joomla heeft voor dat niet-blokkerende deel een ingebouwd mechanisme, dat je in de lettertypesectie ziet: het attribuut rel="lazy-stylesheet".
5.4 Moderne CSS: laat de browser werk overslaan
Twee nieuwere CSS-features geven templates directe renderwinst, zonder JavaScript:
/* Render secties buiten beeld pas als de bezoeker in de buurt scrollt */
.site-footer,
.below-fold-section {
content-visibility: auto;
contain-intrinsic-size: auto 400px; /* reserveer ruimte, voorkom layoutverschuiving */
}
/* Vertel de browser dat de binnenkant van een widget de buitenkant niet raakt */
.module-card {
contain: layout style;
}
content-visibility: auto laat de browser layout en paint overslaan voor content buiten beeld - op lange pagina's verbetert dat de eerste weergave meetbaar. contain beperkt hoe ver layoutherberekeningen doorwerken. Beide zijn veilig toe te voegen in user.css of een child template en degraderen netjes in oudere browsers.
6. JavaScript-strategie
6.1 Blokkeer nooit de parser
Een kale <script src> in de head stopt het HTML-parsen totdat het script gedownload en uitgevoerd is. Joomla's eigen assets vermijden dat: kernscripts laden met defer of als ES-modules (type="module", dat van nature uitstelt - Cassiopeia's template.js staat precies zo gedeclareerd in het assetregister). Volg dezelfde regel voor je eigen scripts: geef ['defer' => true] of ['type' => 'module'] mee als attributen bij het registreren via de Web Asset Manager.
6.2 Bootstrap-JavaScript is opt-in, per onderdeel
Joomla laadt de JavaScript van Bootstrap niet globaal. Elk interactief onderdeel is een apart klein bestand in media/vendor/bootstrap/js/, geactiveerd via een helper:
<?php
use Joomla\CMS\HTML\HTMLHelper;
// Laadt ALLEEN het collapse-script, niet de hele Bootstrap-bundel
HTMLHelper::_('bootstrap.collapse', '.selector');
Roep de helper aan in de override die hem nodig heeft - een accordeon op een pagina hoort geen carrousel-, modal- en dropdowncode naar elke pagina te sturen. Laadt jouw templateframework bootstrap.bundle.min.js in zijn geheel, dan is dat een rode vlag.
6.3 Het eigen script van de template hoort klein te zijn
De volledige template-JavaScript van Cassiopeia is 979 bytes geminificeerd (575 bytes over de lijn met gzip). Hij regelt de terug-naar-boven-knop en weinig meer. Dat is de juiste ambitie voor een template: gedrag hoort bij de componenten en modules die het nodig hebben, conditioneel geladen; het templatescript lijmt alleen de layout aan elkaar. Voor het bredere JavaScript-verhaal (de import map, kernassets, de optionele status van jQuery) is er het aparte JavaScript-artikel.
Naar boven7. Lettertypestrategie: de les van Cassiopeia
Lettertypen zijn vaak de grootste renderblokkerende kostenpost in een template - en de lettertype-aanpak van Cassiopeia is een masterclass die je kunt kopieren. Er zijn vier keuzes, in oplopende volgorde van kosten:
7.1 Optie nul: geen lettertypeschema
De standaard. De template gebruikt zijn ingebouwde stack en downloadt niets. Nul requests, nul bytes, nul layoutverschuiving.
7.2 Systeemfontstacks: gratis en direct
De optie useFontScheme in de templatestijl biedt samengestelde systeemfontstacks (Transitional, Humanist, Geometric, Monospace en meer). Die verwijzen naar lettertypen die al op het apparaat van de bezoeker staan - bijvoorbeeld Inter, Roboto, 'Helvetica Neue', Arial, sans-serif. De tekst rendert direct bij de eerste paint. Voor de meeste zakelijke sites is een goed gekozen systeemstack visueel niet te onderscheiden van een webfont en kost hij niets.
7.3 Lokale lettertypen: zelf gehost, voorgeladen
De optie "Roboto (local)" laadt een stylesheet van je eigen server (media/templates/site/cassiopeia/css/global/fonts-local_roboto.css). Kijk hoe index.php hem laadt:
$wa->registerAndUseStyle('fontscheme.current', $paramsFontScheme,
['version' => 'auto'], ['rel' => 'lazy-stylesheet']);
$this->getPreloadManager()->preload(
$wa->getAsset('style', 'fontscheme.current')->getUri() . '?' . $this->getMediaVersion(),
['as' => 'style']
);
Twee trucs tegelijk: de stylesheet wordt voorgeladen (de browser begint direct met ophalen) maar gelinkt als rel="lazy-stylesheet", zodat hij het renderen niet blokkeert. Joomla's StylesRenderer genereert bovendien een <noscript>-fallback voor bezoekers zonder JavaScript. Zelf hosten houdt je ook aan de veilige kant van de AVG - er gaan geen IP-adressen van bezoekers naar een derde partij alleen om een lettertype op te halen.
7.4 Google Fonts: de volledige schadebeperking
Kies je toch voor de webfont-optie, dan laat Cassiopeia zien hoe je de schade beperkt: het roept preconnect aan voor zowel fonts.googleapis.com als fonts.gstatic.com (zodat DNS, TCP en TLS vroeg gebeuren), laadt de stylesheet voor, en linkt hem opnieuw lui met de noscript-fallback. Kopieer dit patroon voor elke externe resource die je niet kunt vermijden. En in je eigen @font-face-regels: gebruik alleen WOFF2, voeg font-display: swap toe en subset de tekenset als het fontbestand dat toelaat. Overweeg meteen een variabel lettertype: een variabel WOFF2-bestand dekt elk gewicht van licht tot vet, en vervangt zo drie of vier aparte downloads.
8. Afbeeldingen in de templatelaag
8.1 Het logo laadt direct - met opzet
Cassiopeia rendert zijn logo met ['loading' => 'eager', 'decoding' => 'async']. Dat is bewust: het logo staat boven de vouw en is vaak onderdeel van de Largest Contentful Paint. Lazy-load nooit het LCP-element. Een headerafbeelding lui laden is een van de meest voorkomende zelf toegebrachte prestatiewonden - het vertraagt precies de paint die Core Web Vitals meten.
Je kunt nog een stap verder gaan dan eager laden: geef fetchpriority="high" aan de ene afbeelding die je LCP-element is. Waar loading="eager" betekent "stel dit niet uit", zegt fetchpriority="high" tegen de browser: haal dit op voordat andere resources om bandbreedte concurreren. Cassiopeia zet het niet standaard, maar HTMLHelper::_('image') geeft elk attribuut door, dus een override kan het wel.
8.2 Lazy-load onder de vouw, in overrides
Joomla's eigen artikellayouts voegen geen loading="lazy" toe aan intro- en artikelafbeeldingen, dus de templatelaag is de plek waar jij dat doet. Markeer in een blog-override de afbeeldingen vanaf de tweede kaart:
<img src="/..." alt="..."
loading="lazy" decoding="async"
width="800" height="450">
Behoud altijd width en height (of een aspect-ratio-regel in CSS): de browser reserveert dan de ruimte en de layout springt niet wanneer de afbeelding arriveert. Layoutverschuiving is meer een templateprobleem dan een afbeeldingsprobleem.
8.3 Achtergrondafbeeldingen en CSS
Herosecties gebouwd met CSS background-image kunnen geen native lazy loading of srcset gebruiken. Waar de afbeelding inhoud is, gebruik je liever een echte <img> in de markup - dat is sneller, responsive en toegankelijk. Reserveer CSS-achtergronden voor decoratie. Afbeeldingsformaten, compressie en de verkleiningstools van mediabeheer komen aan bod in het algemene prestatie-artikel.
9. Resource hints en de Preload Manager
Joomla verpakt browser-resource-hints in een kleine API op het document: de PreloadManager (libraries/src/Document/PreloadManager.php). Je zag hem al in de lettertypesectie; dit is de volledige gereedschapskist:
<?php
$doc = Joomla\CMS\Factory::getApplication()->getDocument();
$pm = $doc->getPreloadManager();
$pm->preconnect('https://cdn.example.com/'); // open de verbinding vroeg
$pm->dnsPrefetch('https://stats.example.com/'); // los DNS vroeg op (goedkoper)
$pm->preload('/media/.../hero.woff2', // haal een kritiek bestand nu op
['as' => 'font', 'crossorigin' => 'anonymous']);
$pm->prefetch('/waarschijnlijke-volgende-pagina'); // ophalen in luwe tijd, voor later
Een detail dat de meeste ontwikkelaars missen: Joomla print deze hints niet als <link>-tags in de head. De methode preloadAssets() in libraries/src/Document/Document.php serialiseert ze naar een HTTP Link-responseheader. De browser ziet de hints in de headers voordat hij ook maar een byte HTML geparsed heeft - eerder dan welke head-tag dan ook. Het betekent ook dat goed geconfigureerde servers er HTTP 103 Early Hints van kunnen maken.
Gebruik hints spaarzaam. Alles voorladen is niets voorladen: elke hint concurreert om dezelfde bandbreedte als de pagina zelf. Twee of drie hints voor echt kritieke resources (het hoofdlettertype, een externe origin) is de juiste maat.
Naar boven10. Caching op templateniveau
10.1 Modulecache-modi
Omdat modules renderen tijdens de template-output (sectie 2), is modulecaching caching op templatelaagniveau. Een module die caching ondersteunt kan een van vier modi gebruiken, afgehandeld in ModuleHelper::moduleCache():
| Modus | Cachesleutel | Typisch gebruik |
|---|---|---|
static |
Een cache-item voor alle pagina's met dezelfde moduleparameters | Footertekst, banners |
itemid |
Een item per menu-item | Menu's, kruimelpaden |
safeuri |
Item per toegestane URL-parameters | Gefilterde lijsten |
id |
De module berekent zijn eigen sleutel | Complexe maatwerkmodules |
Modulecaching schakelt automatisch uit voor ingelogde gebruikers. Ontwikkel je modules: de juiste cachemodus declareren in je module is een groter cadeau aan sitebeheerders dan welke micro-optimalisatie in code dan ook.
10.2 Wat paginacaching met je template doet
Met volledige paginacaching (de plugin Systeem - Paginacache) draait de PHP van je template helemaal niet bij cachehits - de opgeslagen HTML wordt direct geserveerd. Daarmee wordt de output van je template het ding dat gecachet wordt: een slanke pagina van 60 KB cachet en serveert sneller dan een van 400 KB, zelfs vanuit cache. Cachelagen en hun valkuilen hebben een eigen artikel; het punt hier is dat paginacaching een zware template niet excuseert - het verplaatst de kosten alleen.
Naar boven11. De template meten
11.1 Scheid de twee helften
Splits in je meettool de statistieken: TTFB hoort bij de serverhelft; alles daarna (First Contentful Paint, Largest Contentful Paint, totale blokkeertijd, layoutverschuiving) hoort vooral bij de templatehelft. Een pagina met een TTFB van 200 ms en een LCP van 4 seconden heeft een templateprobleem, en geen enkele servertuning lost dat op.
Scheid ook labdata van velddata. Lighthouse draait een eenmalige test op een gesimuleerd apparaat (lab); het Core Web Vitals-rapport in Google Search Console en het bovenste blok van PageSpeed Insights tonen wat echte bezoekers de afgelopen 28 dagen hebben gemeten (veld). Een goede labscore in combinatie met falende velddata betekent meestal dat je echte publiek tragere apparaten of netwerken gebruikt dan je test. Vertrouw de veldcijfers - die tellen voor de ranking.
11.2 Drie browsertools die zichzelf terugverdienen
- Lighthouse - de totaalscore, plus concrete signalen: renderblokkerende resources, ongebruikte CSS/JS, DOM-omvang, ontbrekende afbeeldingsafmetingen.
- Coverage-paneel (DevTools) - toont per CSS/JS-bestand het percentage dat op de huidige pagina echt gebruikt wordt. Genadeloos eerlijk over frameworkballast.
- Netwerkthrottling - test op "Slow 4G" met CPU-throttling. De telefoons van je bezoekers zijn trager dan je ontwikkelmachine; een template is pas snel als hij daar snel is.
11.3 Joomla's eigen blik
Zet de plugin Systeem - Foutopsporing aan en lees de profilerbalk: afterRenderModule-regels tonen wat elke modulepositie kost in milliseconden, en de assetlijst toont elk bestand dat de Web Asset Manager besloot te laden, met de afhankelijkheidsketen. Het is de snelste manier om die ene module of asset te vinden die niet op deze pagina thuishoort.
12. Templateprestaties en de Web Services API
De API-applicatie gebruikt je template helemaal niet. Een request naar /api/index.php/v1/content/articles rendert via de klasse JsonapiDocument - geen index.php, geen Web Asset Manager, geen modules, geen chrome. Dat heeft twee praktische gevolgen. Ten eerste: template-optimalisatie doet niets voor API-consumenten, dus bij headless verschuift het hele browserbudget naar het frontend-framework dat je daar bouwt. Ten tweede, omgekeerd: een API-intensieve site heeft nog steeds een snelle template nodig voor de pagina's die mensen bezoeken - de twee prestatiebudgetten staan los van elkaar.
curl -H "X-Joomla-Token: <token>" \
https://example.test/api/index.php/v1/content/articles
# Pure JSON: geen enkel template-asset betrokken
Naar boven13. SEO en metadata
Core Web Vitals zijn een rankingsignaal, en de template bepaalt alle drie: de Largest Contentful Paint (lettertypen, renderblokkerende CSS, de heroafbeelding), Cumulative Layout Shift (afbeeldingsafmetingen, fontwissels, laat ladende banners) en de reactiesnelheid op interactie (JavaScript-gewicht). Een templateverbouwing is daarmee net zo goed een SEO-project als een designproject.
Twee templatespecifieke SEO-details: houd de <meta name="viewport">-tag intact (Cassiopeia zet width=device-width, initial-scale=1; zonder die tag zakt je site direct voor mobielvriendelijkheid), en zorg dat inhoud achter accordeons of tabs gewoon in de HTML staat - zoekmachines indexeren die prima, maar inhoud die pas na interactie door JavaScript wordt ingeladen is riskanter. Slanke, semantische markup met een enkele <h1> en een logische koppenboom helpt crawlers en schermlezers allebei.
14. Veelvoorkomende fouten en valkuilen
14.1 Alles laadt overal
Symptoom: Het galerijscript, de kaartenbibliotheek en de slider-CSS laden op elke pagina, ook op het contactformulier.
Oplossing: Verplaats paginaspecifieke assets uit index.php naar de overrides van de views die ze gebruiken (sectie 3.3). De Web Asset Manager houdt de volgorde correct, waar je ze ook registreert.
14.2 De @import-keten
Symptoom: user.css begint met drie @import-regels; stylesheets laden na elkaar in plaats van parallel, en de pagina verschijnt laat.
Oplossing: Registreer elke stylesheet via de Web Asset Manager (of combineer ze in een gecompileerd bestand in een child template). @import zet downloads in een rij; de assetgraaf parallelliseert ze.
14.3 De hero lui laden
Symptoom: Lighthouse rapporteert een slechte LCP terwijl afbeeldingen "geoptimaliseerd" zijn; de headerafbeelding popt zichtbaar in beeld.
Oplossing: Verwijder loading="lazy" van alles boven de vouw. Volg het voorbeeld van Cassiopeia: het logo laadt met loading="eager". Lazy-load alleen wat buiten beeld begint.
14.4 Een iconenfont voor drie iconen
Symptoom: De template haalt de volledige iconenfont-CSS binnen (zo'n 100 KB in de standaardopzet) om een zoekicoon, een telefoon en een pijltje te tonen.
Oplossing: Gebruik inline SVG voor een handvol iconen. Gebruikt je child template de icoonklassen helemaal niet, registreer dan een templatestylesheet zonder de fontawesome-afhankelijkheid in plaats van hem te erven.
14.5 Alleen testen op een snelle machine
Symptoom: "Bij mij is hij snel", maar echte bezoekers op middenklasse-telefoons wachten seconden.
Oplossing: Test altijd met CPU- en netwerkthrottling aan, en bekijk de velddata (Core Web Vitals) in Google Search Console - die weerspiegelt echte apparaten, niet je werkstation.
14.6 De blinde optimalisatieplugin
Symptoom: Een minify-en-combineer-extensie staat aan; sommige pagina's breken, ES-modulescripts werken niet meer, en de "geoptimaliseerde" bundel verandert per pagina zodat er niets cachet.
Oplossing: Joomla levert al geminificeerde assets, voorgecomprimeerde .gz-varianten, een loader die afhankelijkheden ordent en versies voor cache busting. Repareer eerst de templatelaag; zet een optimalisatieplugin alleen in voor een gemeten, resterend probleem - en meet opnieuw na het inschakelen.
15. Best practices
Als je maar een paar dingen uit dit artikel onthoudt, onthoud dan deze:
- Meet eerst, en splits de helften: TTFB is de server, alles daarna is vooral de template.
- Registreer elk asset via de Web Asset Manager, met
deferoftype="module"voor scripts en['version' => 'auto']voor cache busting. - Laad paginaspecifieke assets in overrides, niet in
index.php. - Zet
countModules()-controles om moduleposities en kies de lichtste chrome die werkt. - Kies bij voorkeur systeemfontstacks; heb je webfonts nodig, host ze dan zelf en kopieer Cassiopeia's patroon van preload plus lazy-stylesheet.
- Lazy-load nooit het LCP-element; zet altijd afbeeldingsafmetingen.
- Gebruik
content-visibility: autoop lange secties onder de vouw - een CSS-regel voor echte renderwinst. - Houd de eigen JavaScript van de template piepklein; Cassiopeia redt het met minder dan 1 KB.
- Test met throttling, en lees het Coverage-paneel voordat je een nieuwe bibliotheek toevoegt.
16. In het kort
RENDER-PIJPLIJN
component draait eerst → template index.php → jdoc-tags geparsed
→ modules renderen per positie → head als laatste samengesteld
WEB ASSET MANAGER (in index.php of een override)
$wa = $this->getWebAssetManager(); // template
$wa = $this->getDocument()->getWebAssetManager(); // override
$wa->usePreset('template.cassiopeia.ltr');
$wa->useScript('bootstrap.collapse'); // een onderdeel, niet de bundel
$wa->registerAndUseScript('my.js', 'path/file.js',
['version' => 'auto'], ['defer' => true]);
$wa->addInlineStyle(':root { --brand: #001b4c; }');
RESOURCE HINTS (verstuurd als HTTP Link-header)
$doc->getPreloadManager()->preconnect('https://origin/');
$doc->getPreloadManager()->preload('/pad/font.woff2',
['as' => 'font', 'crossorigin' => 'anonymous']);
LETTERTYPE-LADDER (goedkoopste eerst)
geen → systeemstack → zelf gehost (preload + lazy-stylesheet)
→ Google Fonts (preconnect x2 + preload + lazy-stylesheet)
TEMPLATE-CHECKLIST
[ ] countModules() om elke optionele positie
[ ] lichtste chrome (card / noCard / none) per positie
[ ] paginaspecifieke assets verplaatst naar overrides
[ ] loading="eager" + fetchpriority="high" op het LCP-element, lazy onder de vouw
[ ] width + height op elke layoutafbeelding
[ ] content-visibility: auto op lange footers/secties
[ ] getest met CPU- + netwerkthrottling
KERNBESTANDEN
templates/<tpl>/joomla.asset.json assetregister
templates/<tpl>/index.php pijplijn + assetactivatie
templates/<tpl>/html/layouts/chromes/ modulewrappers
libraries/src/Document/HtmlDocument.php jdoc-parsing
libraries/src/Document/Renderer/Html/ de renderers
libraries/src/Document/PreloadManager.php resource hints
Naar boven17. Samenvatting
- De server bepaalt de TTFB; de template bepaalt bijna alles wat de bezoeker daarna ervaart.
- Joomla rendert eerst het component, draait dan je templatebestand en vervangt daarna de
<jdoc:include>-tags - modules renderen op dat moment, de head als laatste. - De Web Asset Manager is het centrale prestatiegereedschap: benoemde assets, afhankelijkheidsvolgorde, presets, conditioneel laden in overrides en automatische cache busting.
- Slanke markup betaalt dubbel uit: minder DOM-nodes renderen sneller en zijn makkelijker toegankelijk te maken.
countModules()en lichte chromes houden de boom klein. - Lettertypen volgen een kostenladder van "geen" tot Google Fonts; Cassiopeia demonstreert het schadebeperkingspatroon (preconnect, preload, lazy stylesheet) als je die ladder beklimt.
- Resource hints reizen als een HTTP
Link-header, voor de HTML zelf uit - krachtig, en het best spaarzaam gebruikt. - Modulecache-modi, loading-attributen op afbeeldingen en moderne CSS zoals
content-visibilitymaken de gereedschapskist compleet.
Een snelle template is vooral de som van kleine, bewuste beslissingen: een stylesheet minder, een script met defer, een overgeslagen modulewrapper. Blijven je scores laag na het tunen en vermoed je dat de template of zijn extensies de oorzaak zijn, dan loont het om de renderpijplijn systematisch te laten doormeten - de bottleneck zit meestal op de laatste plek waar de sitebouwer keek.
Naar boven

Peter is Joomla specialist en Linux admin voor snelle, veilige en schaalbare websites.












