Umbraco website traag? 7 oorzaken en hun fixes
Een Umbraco-site die traag aanvoelt, is zelden één probleem. Het is een stapeling van kleine beslissingen die ooit snel genoeg leken. Hier zijn de zeven oorzaken die we in de praktijk het vaakst tegenkomen — plus de fix per stuk.
Het korte antwoord
Een trage Umbraco-site is bijna nooit één probleem. Het is een stapeling: een paar afbeeldingen die stiekem te groot zijn, een vergeten cache-header, een Razor-view die per ongeluk tweeduizend content-nodes doorloopt om één menu-item te renderen. Elk afzonderlijk onschuldig. Bij elkaar opgeteld: een First Contentful Paint van 4,8 seconden en een redactie die denkt dat "Umbraco nu eenmaal traag is".
Dat klopt niet. Umbraco is standaard snel. Een goed opgezette site serveert de homepage in onder de seconde, ook onder load. Maar na drie jaar content toevoegen, vier reorganisaties van templates en twee nieuwe developers in het team, hoopt er iets op. Wij noemen dat performance-drift — hetzelfde verschijnsel dat elk oud huis beslaat zodra er niemand meer onderhoud pleegt.
In ons audit-werk komen dezelfde zeven oorzaken telkens terug. Fix er vier van en je zit meestal weer onder de anderhalve seconde. Fix ze allemaal en je site laadt sneller dan die van concurrenten die nog niet eens weten waar ze moeten beginnen. Dit is de lijst die wij zelf gebruiken tijdens een intake — zonder buzzwords, met per oorzaak de fix en een realistische inschatting van de winst.
Oorzaak 1: media die stiekem te groot is
Hoe je het herkent. Open DevTools, tab Network, sorteer op size. Als de zwaarste drie requests op je homepage afbeeldingen zijn en minstens één is groter dan 500 KB, sta je hier. Bijna elke site die wij binnenkomen heeft ergens in het CMS een plaatje van 3,2 MB staan dat een redacteur ooit rechtstreeks uit Canva heeft geüpload omdat "4K voor retina hoort".
Waarom het bestaat. Umbraco doet bij upload standaard géén agressieve compressie. De image-cropper serveert wel efficiënt, maar alleen als je queries expliciet om een breedte en quality vragen. Bij upgrades van oudere versies blijft er vaak een stuk code achter dat de originele bestanden rechtstreeks aan de browser geeft. Dan lijkt alles goed geconfigureerd, maar in de praktijk gaan er tientallen megabytes per sessie over de lijn.
De fix. Drie stappen, in deze volgorde. Eén: zet je GetCropUrl-aanroepen overal op een expliciete width en quality (meestal quality=75). Twee: schakel WebP-output in via de image-cropper-config. Drie: draai een bulk-verkleining over je Media-library voor assets boven de megabyte. Verwacht op basis hiervan alleen al 40 tot 65 procent LCP-winst. Dit is altijd de eerste fix — maximale winst per uur werk.
Oorzaak 2: geen of verkeerd ingestelde output-cache
Hoe je het herkent. Een tweede bezoek aan dezelfde pagina is bijna even traag als het eerste. Server-CPU piekt bij elke request, ook op content die wekelijks wijzigt. Als je Umbraco-logs per minuut tientallen template-renders vastleggen voor dezelfde URL, zit hier je vertraging.
Waarom het bestaat. Umbraco rendert standaard op elke request opnieuw. Dat is goed — je wilt een CMS dat altijd verse content laat zien. Maar zonder Cache-Control-headers, ResponseCache-attributen op je controllers of een output-cache vóór je applicatie, doet elke browser-refresh het volledige werk opnieuw. Op pagina's die per dag één keer wijzigen is dat zonde van de rekenkracht en onnodig traag voor de bezoeker.
De fix. Voeg [ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any, VaryByHeader = "Accept-Encoding")] toe aan je surface- en API-controllers. Zet een cache-control: public, max-age=3600-header voor statische content via je hosting-pipeline. Overweeg voor high-traffic sites een reverse proxy (Cloudflare, Varnish of Azure Front Door) met een cache-regel van een paar minuten. In de praktijk zakt de Time-to-First-Byte op de zwaarste templates van 2,4 seconden naar onder de 600 ms.
Oorzaken 3 en 4: content-queries en zware Razor-views
Deze twee zitten dicht op elkaar en ik behandel ze daarom samen. De symptomen zijn identiek: de eerste render duurt lang, de CPU staat op 100 procent en bij iets meer verkeer klapt de server in tweeën.
Hoe je het herkent. MiniProfiler laat zien dat één request meer dan 200 content-lookups doet. Of een Razor-view die op elke pagina .Descendants() aanroept op de site-root om een menu te bouwen. Of een custom-component dat per item opnieuw de cache raadpleegt zonder die resultaten naar de view door te geven.
Waarom het bestaat. In het begin leek het onschuldig. Eén descendant-call hier, een Children() daar. Maar het model dat er toen was (twintig pagina's) is inmiddels uitgegroeid tot tweeduizend nodes, en dezelfde view doet dat werk nu op elke request. Zware Razor-loops zijn bovendien vaak het gevolg van copy-paste: iemand bouwt een nieuw blok op basis van een bestaande template en neemt per ongeluk een niet-relevante query mee.
De fix. Meet eerst met MiniProfiler of Umbraco's ingebouwde profiling. Vervang .Descendants() op de root door een gerichte DescendantsOfType() op de juiste subtree, of door een IDocumentNavigationQueryService-aanroep. Sla menu-, footer- en andere globaal gebruikte data op in een IMemoryCache met een afhankelijkheid van ContentPublished-events, zodat je cache ongeldig wordt bij elke publicatie. Na deze fix verdwijnt 60 tot 90 procent van je CPU-tijd op de backend. Wij hebben dit zien gebeuren op sites waar niets anders is veranderd.
Veel teams beginnen met de fix die het lekkerst klinkt — beeldcompressie, een nieuwe CDN, eindelijk dat cache-verhaal. Zonder baseline-metingen weet je pas achteraf of je echt gewonnen hebt. Draai een Lighthouse-run op je vijf belangrijkste templates vóór je iets aanraakt en herhaal hem na elke fix. Dat is twee minuten werk en voorkomt achteraf dagen aan discussie over of de ingreep echt iets heeft gedaan.
Oorzaak 5: JavaScript-overgewicht
Hoe je het herkent. Lighthouse geeft een rode Total Blocking Time. Je bundle.js staat boven de 500 KB gzipped. Je laadt jQuery, Bootstraps volledige JS-bundel én een carousel-library terwijl die carousel alleen op één pagina voorkomt.
Waarom het bestaat. Elke developer die aan de site werkte, voegde een library toe voor één specifieke feature. Bundle-splitting werd nooit doorgevoerd omdat "het werkt toch". En de laatste keer dat iemand serieus naar de dependencies heeft gekeken is minstens twee developers geleden.
De fix. Strip eerst wat je niet gebruikt. Een moderne Umbraco-site hoort geen jQuery meer te bevatten — Vanilla JavaScript volstaat voor 95 procent van het UI-werk. Vervolgens: splits je bundle per template (home.js, contact.js, artikel.js), gebruik type="module" met dynamic imports voor componenten die alleen op bepaalde pagina's leven, en laad third-party scripts (analytics, chat, cookiebanner) met async of defer. Voor sites die op mobiel scoren is dit vaak de fix waar de meeste Lighthouse-punten uit komen.
Oorzaken 6 en 7: hosting zonder CDN en database-bloat
Hosting. Een Nederlandse site gehost in West-Europa bedient Nederlands publiek prima. Zodra je buiten de Benelux verkeer krijgt — België, Duitsland, internationale sales — worden je Time-to-First-Byte-cijfers ineens inconsistent. Zet een CDN voor je site (Cloudflare heeft een uitstekende free tier, Azure Front Door is enterprise-niveau) en je statische assets worden vanaf het dichtstbijzijnde edge-punt geserveerd. Typische winst op internationale gebruikers: 40 tot 120 ms per request.
Database-bloat is de minst zichtbare oorzaak, maar bij oudere sites soms een halve seconde aan TTFB waard. De umbracoAudit-tabel groeit eindeloos. Oude content-versions blijven eeuwig staan. Log-tabellen slokken gigabytes op. Je uSync-folder staat vol met migraties uit 2021 die niemand meer begrijpt.
De fix. Schakel de Umbraco Content Version Cleanup in (appsettings.json: Umbraco:CMS:ContentVersionCleanupPolicy) met een redelijke retentie — zes weken is voor de meeste organisaties genoeg. Ruim de audit- en log-tabellen op met een nightly job. Loop je uSync-folder door op bestanden die al gedeployed zijn en weg kunnen. Redacteuren merken het effect niet direct op de frontend, maar de backoffice-ervaring wordt beduidend sneller en de TTFB op de publieke site zakt met 200 tot 500 ms.
Zo pak je een audit aan
-
Draai Lighthouse op je vijf belangrijkste templates, zet de scores in een spreadsheet en prioriteer op bezoekersaantal × potentiële winst. Zonder deze stap fix je onvermijdelijk de verkeerde dingen eerst.
-
Images, output-cache en async-JavaScript-tags. Deze drie samen dekken meestal 60 tot 75 procent van de totale winst. Elke fix is binnen een dag te testen en terug te draaien als iets breekt.
-
Razor-views refactoren, N+1-queries oplossen, database-cleanup inplannen, CDN-config doorvoeren. Dit is het werk waar je een partner voor inhuurt die je code kent. Budgetteer het vooraf, dan landt het ook binnen.
Wat het oplevert en waar je begint
Je hoeft zelden alle zeven fixes in één project door te voeren. De 80/20-regel werkt hier extreem goed: beeld-optimalisatie en output-cache samen halen in de meeste audits al 70 procent van de totale winst binnen. De resterende vijf oorzaken pak je op in latere sprints, op het moment dat metrics laten zien dat het nodig is.
De enige regel die echt niet onderhandelbaar is: beslis niet op gevoel. Redacteuren voelen trage sites op heel andere plekken dan bezoekers. Developers voelen sites op hun eigen kabel. De enige meting die telt, is die van je echte gebruikers op hun echte devices — Google's Core Web Vitals (CrUX) of een real-user-monitoring-tool als SpeedCurve geven je die. Van daaruit werk je in volgorde van grootste impact.
Lukt het je niet om zelf tijd vrij te maken voor zo'n audit? Dat is onze Umbraco specialist-dienst in één zin: twee weken intensief meten, fixen en overdragen, met een rapport erna waarin per fix staat wat de impact was en welk werk nog uitstaat. Neem contact op voor een vrijblijvende intake — we kijken kosteloos mee naar een Lighthouse-run van je huidige site en zeggen eerlijk of een audit zinvol is. Soms is het antwoord "nee, fix eerst dat ene zelf". Ook dat is een nuttig gesprek.