I recently rebuilt my personal site, www.stefan-andres.at, from scratch. It runs on TYPO3 13.4 with the Bootstrap Package, and the goal was simple: a light, calm design – and a site that scores well where it matters for users, not just in screenshots.
This isn't a "look at my new website" post. It's a list of the things that actually changed the numbers, with code you can copy.
Where I ended up (mobile, PageSpeed Insights):
- Accessibility: 100
- Best Practices: 100
- SEO: 100
- Performance: 96 (88 at launch, 95 after the font and contrast fixes, 77 after adding Analytics – more on that below)
1. Self-host your fonts – and get the file names right
The biggest single render-blocking item was Google Fonts: one CSS request to fonts.googleapis.com, then a chain to fonts.gstatic.com for every weight. Roughly 750 ms on a throttled mobile connection.
I downloaded the .woff2 files, put them in fileadmin, and wrote one @font-face per weight:
@font-face{font-family:"Plus Jakarta Sans";font-weight:400;font-display:swap;src:url(/fileadmin/Daten/fonts/PlusJakartaSans-Regular.woff2) format("woff2")}
@font-face{font-family:"Plus Jakarta Sans";font-weight:700;font-display:swap;src:url(/fileadmin/Daten/fonts/PlusJakartaSans-Bold.woff2) format("woff2")}
@font-face{font-family:"Plus Jakarta Sans";font-weight:800;font-display:swap;src:url(/fileadmin/Daten/fonts/PlusJakartaSans-ExtraBold.woff2) format("woff2")}
/* … 500, 600 and the italic display face */
Two lessons:
-
Check the file names. My first version pointed to a single
plus-jakarta-sans-latin.woff2that didn't exist – the site silently fell back to system fonts. -
Remove the old
@import. It was still sitting at the top of the main stylesheet and kept loading Google Fonts in parallel.
Then preload only the fonts visible above the fold:
page.headerData.5 = TEXT
page.headerData.5.value (
<link rel="preload" href="/fileadmin/Daten/fonts/PlusJakartaSans-ExtraBold.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fileadmin/Daten/fonts/PlayfairDisplay-Italic.woff2" as="font" type="font/woff2" crossorigin>
)
Bonus: no third-party font requests means one less entry in the privacy policy and a simpler Content-Security-Policy.
2. Load page-specific CSS and JS only where it's needed
My site has ten free browser tools (contrast checker, favicon generator, minifiers, …). Their CSS and JavaScript were loaded on every page – including the home page, where none of them run.
TypoScript conditions make this a one-liner. All tools live below one page (uid 174):
[174 in tree.rootLineIds]
page.includeCSS.tools = fileadmin/Template/css/cust03-tools.css
page.includeJSFooter.customtools = fileadmin/Template/js/tools.js
page.includeJSFooter.customtools.defer = 1
[END]
tree.rootLineIds matches the page itself and everything below it, so new tools are covered automatically.
3. Load Google Analytics only after consent
Adding GA4 the "standard" way dropped my performance score from 95 to 77. gtag.js is ~177 KB and Lighthouse flags most of it as unused.
But my cookie banner is opt-in anyway – until someone clicks "Allow", GA doesn't collect anything. So why load it?
<script>
window.dataLayer=window.dataLayer||[];
function gtag(){dataLayer.push(arguments);}
gtag('consent','default',{'ad_storage':'denied','ad_user_data':'denied','ad_personalization':'denied','analytics_storage':'denied'});
function loadGA(){
if(window.gaLoaded)return; window.gaLoaded=1;
gtag('consent','update',{'analytics_storage':'granted'});
var s=document.createElement('script');
s.async=1; s.src='https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX';
document.head.appendChild(s);
gtag('js',new Date()); gtag('config','G-XXXXXXX');
}
// returning visitors who already agreed
if(document.cookie.indexOf('cookieconsent_status=allow')>-1){loadGA();}
// first-time visitors clicking "Allow"
document.addEventListener('click',function(e){if(e.target.closest('.cc-allow')){loadGA();}});
</script>
cookieconsent_status and .cc-allow are the defaults of the cookie banner shipped with the Bootstrap Package – check your own cookie name in DevTools → Application → Cookies.
Result: Lighthouse (which never clicks "Allow") doesn't see GA at all, real visitors who consent are tracked normally, and it's cleaner from a GDPR point of view. Score back to 96.
4. Accessibility: contrast was the whole story
The audit flagged two things: contrast and heading order.
Contrast
My brand orange #df532e with white text sits at about 3.6:1 – below the WCAG AA minimum of 4.5:1 for normal text. Instead of changing the brand colour, I added a second token just for text and button backgrounds:
:root{
--sa-orange:#df532e; /* decoration: dots, lines, borders */
--sa-orange-a11y:#c74523; /* text and filled buttons: ~4.9:1 on white */
}
The less obvious trap: grey on dark. My footer used the same muted grey as the light sections. Darkening that grey (to fix light backgrounds) made the footer worse. Dark backgrounds need their own, lighter muted tone.
Heading order
Two culprits:
- A card in the hero used an
<h3>right after the<h1>– visually fine, semantically a skipped level. It's now a<p>with a class. - The Bootstrap Package constant
defaultHeaderType = 5turned every content element without an explicit header type into an<h5>– including my footer. Setting it to2fixed that globally.
5. Small things that added up
-
Images as WebP with explicit
width/height→ CLS dropped to 0. -
fetchpriority="high"on the first visible image,loading="lazy"on the rest. -
One CSS custom-property system (
--sa-ink,--sa-gray,--sa-shadow-card, …) instead of hard-coded values. That made the contrast fixes a two-line change. -
Mobile menu on iOS: a
position:fixedmenu inside a header withbackdrop-filterrendered invisible in Safari. Anchoring it withposition:absolute; top:100%relative to the header fixed it.
What's on the site now
Besides services and a portfolio, the site doubles as a small toolbox:
-
10 free browser tools – palette & contrast checker, favicon generator, image optimizer, SEO check, CSS/JS/SVG minifiers, a
clamp()calculator, JSON formatter, a TYPO3 Fluid ViewHelper generator and a client-briefing generator - 3 apps for iPhone & Mac – no account, no ads, data stays on the device
- A glossary of print, web and video terms
If you work with TYPO3 or just want a quick contrast check, have a look: 👉 stefan-andres.at/tools
I'd love to hear what you'd improve – or what moved your Lighthouse scores the most.


Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.