<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: stefan andres</title>
    <description>The latest articles on DEV Community by stefan andres (@stefan_andres_07).</description>
    <link>https://dev.to/stefan_andres_07</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4091595%2Fb7e1e6eb-4139-4d0c-a3de-1ae0e395a4ba.png</url>
      <title>DEV Community: stefan andres</title>
      <link>https://dev.to/stefan_andres_07</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stefan_andres_07"/>
    <language>en</language>
    <item>
      <title>Redesigning my TYPO3 portfolio – what actually moved the Lighthouse scores</title>
      <dc:creator>stefan andres</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:49:17 +0000</pubDate>
      <link>https://dev.to/stefan_andres_07/redesigning-my-typo3-portfolio-what-actually-moved-the-lighthouse-scores-11k7</link>
      <guid>https://dev.to/stefan_andres_07/redesigning-my-typo3-portfolio-what-actually-moved-the-lighthouse-scores-11k7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frydh8g6uvzhg57ibjz8n.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frydh8g6uvzhg57ibjz8n.jpg" alt="Homepage of stefan-andres.at – light hero with the headline &amp;quot;Design that lets your brand speak&amp;quot;" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently rebuilt my personal site, &lt;a href="https://www.stefan-andres.at" rel="noopener noreferrer"&gt;www.stefan-andres.at&lt;/a&gt;, from scratch. It runs on &lt;strong&gt;TYPO3 13.4&lt;/strong&gt; with the &lt;strong&gt;Bootstrap Package&lt;/strong&gt;, and the goal was simple: a light, calm design – and a site that scores well where it matters for users, not just in screenshots.&lt;/p&gt;

&lt;p&gt;This isn't a "look at my new website" post. It's a list of the things that &lt;strong&gt;actually&lt;/strong&gt; changed the numbers, with code you can copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I ended up (mobile, PageSpeed Insights):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accessibility: &lt;strong&gt;100&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Best Practices: &lt;strong&gt;100&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;SEO: &lt;strong&gt;100&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Performance: &lt;strong&gt;96&lt;/strong&gt; (88 at launch, 95 after the font and contrast fixes, 77 after adding Analytics – more on that below)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Self-host your fonts – and get the file names right
&lt;/h2&gt;

&lt;p&gt;The biggest single render-blocking item was Google Fonts: one CSS request to &lt;code&gt;fonts.googleapis.com&lt;/code&gt;, then a chain to &lt;code&gt;fonts.gstatic.com&lt;/code&gt; for every weight. Roughly 750 ms on a throttled mobile connection.&lt;/p&gt;

&lt;p&gt;I downloaded the &lt;code&gt;.woff2&lt;/code&gt; files, put them in &lt;code&gt;fileadmin&lt;/code&gt;, and wrote one &lt;code&gt;@font-face&lt;/code&gt; per weight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@font-face&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;"Plus Jakarta Sans"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;font-display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fileadmin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Daten&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fonts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;PlusJakartaSans-Regular&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;woff2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;"woff2"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;@font-face&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;"Plus Jakarta Sans"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;700&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;font-display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fileadmin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Daten&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fonts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;PlusJakartaSans-Bold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;woff2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;"woff2"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;@font-face&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;"Plus Jakarta Sans"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;800&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;font-display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fileadmin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Daten&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fonts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;PlusJakartaSans-ExtraBold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;woff2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;"woff2"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="c"&gt;/* … 500, 600 and the italic display face */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check the file names.&lt;/strong&gt; My first version pointed to a single &lt;code&gt;plus-jakarta-sans-latin.woff2&lt;/code&gt; that didn't exist – the site silently fell back to system fonts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove the old &lt;code&gt;@import&lt;/code&gt;.&lt;/strong&gt; It was still sitting at the top of the main stylesheet and kept loading Google Fonts in parallel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then preload only the fonts visible above the fold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page.headerData.5 = TEXT
page.headerData.5.value (
&amp;lt;link rel="preload" href="/fileadmin/Daten/fonts/PlusJakartaSans-ExtraBold.woff2" as="font" type="font/woff2" crossorigin&amp;gt;
&amp;lt;link rel="preload" href="/fileadmin/Daten/fonts/PlayfairDisplay-Italic.woff2" as="font" type="font/woff2" crossorigin&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bonus: no third-party font requests means one less entry in the privacy policy and a simpler Content-Security-Policy.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Load page-specific CSS and JS only where it's needed
&lt;/h2&gt;

&lt;p&gt;My site has ten free browser tools (contrast checker, favicon generator, minifiers, …). Their CSS and JavaScript were loaded on &lt;strong&gt;every&lt;/strong&gt; page – including the home page, where none of them run.&lt;/p&gt;

&lt;p&gt;TypoScript conditions make this a one-liner. All tools live below one page (uid 174):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[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]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tree.rootLineIds&lt;/code&gt; matches the page itself and everything below it, so new tools are covered automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Load Google Analytics only after consent
&lt;/h2&gt;

&lt;p&gt;Adding GA4 the "standard" way dropped my performance score from 95 to 77. &lt;code&gt;gtag.js&lt;/code&gt; is ~177 KB and Lighthouse flags most of it as unused.&lt;/p&gt;

&lt;p&gt;But my cookie banner is &lt;strong&gt;opt-in&lt;/strong&gt; anyway – until someone clicks "Allow", GA doesn't collect anything. So why load it?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataLayer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataLayer&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;&lt;span class="nx"&gt;dataLayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);}&lt;/span&gt;
&lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ad_storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ad_user_data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ad_personalization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;analytics_storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadGA&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gaLoaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gaLoaded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;analytics_storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;granted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;G-XXXXXXX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// returning visitors who already agreed&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cookieconsent_status=allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="nf"&gt;loadGA&lt;/span&gt;&lt;span class="p"&gt;();}&lt;/span&gt;
&lt;span class="c1"&gt;// first-time visitors clicking "Allow"&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.cc-allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;&lt;span class="nf"&gt;loadGA&lt;/span&gt;&lt;span class="p"&gt;();}});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cookieconsent_status&lt;/code&gt; and &lt;code&gt;.cc-allow&lt;/code&gt; are the defaults of the cookie banner shipped with the Bootstrap Package – check your own cookie name in DevTools → Application → Cookies.&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;96&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Accessibility: contrast was the whole story
&lt;/h2&gt;

&lt;p&gt;The audit flagged two things: &lt;strong&gt;contrast&lt;/strong&gt; and &lt;strong&gt;heading order&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contrast
&lt;/h3&gt;

&lt;p&gt;My brand orange &lt;code&gt;#df532e&lt;/code&gt; with white text sits at about &lt;strong&gt;3.6:1&lt;/strong&gt; – 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--sa-orange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;#df532e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c"&gt;/* decoration: dots, lines, borders */&lt;/span&gt;
  &lt;span class="py"&gt;--sa-orange-a11y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;#c74523&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;/* text and filled buttons: ~4.9:1 on white */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The less obvious trap: &lt;strong&gt;grey on dark&lt;/strong&gt;. My footer used the same muted grey as the light sections. Darkening that grey (to fix light backgrounds) made the footer &lt;em&gt;worse&lt;/em&gt;. Dark backgrounds need their own, lighter muted tone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heading order
&lt;/h3&gt;

&lt;p&gt;Two culprits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A card in the hero used an &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; right after the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; – visually fine, semantically a skipped level. It's now a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; with a class.&lt;/li&gt;
&lt;li&gt;The Bootstrap Package constant &lt;code&gt;defaultHeaderType = 5&lt;/code&gt; turned every content element without an explicit header type into an &lt;code&gt;&amp;lt;h5&amp;gt;&lt;/code&gt; – including my footer. Setting it to &lt;code&gt;2&lt;/code&gt; fixed that globally.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Small things that added up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Images as WebP&lt;/strong&gt; with explicit &lt;code&gt;width&lt;/code&gt;/&lt;code&gt;height&lt;/code&gt; → CLS dropped to 0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fetchpriority="high"&lt;/code&gt;&lt;/strong&gt; on the first visible image, &lt;code&gt;loading="lazy"&lt;/code&gt; on the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One CSS custom-property system&lt;/strong&gt; (&lt;code&gt;--sa-ink&lt;/code&gt;, &lt;code&gt;--sa-gray&lt;/code&gt;, &lt;code&gt;--sa-shadow-card&lt;/code&gt;, …) instead of hard-coded values. That made the contrast fixes a two-line change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile menu on iOS:&lt;/strong&gt; a &lt;code&gt;position:fixed&lt;/code&gt; menu inside a header with &lt;code&gt;backdrop-filter&lt;/code&gt; rendered invisible in Safari. Anchoring it with &lt;code&gt;position:absolute; top:100%&lt;/code&gt; relative to the header fixed it.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcigsda3605u0hdvzcnab.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcigsda3605u0hdvzcnab.jpg" alt="Tools &amp;amp; Apps section – ten free browser tools and three apps for iPhone &amp;amp; Mac" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's on the site now
&lt;/h2&gt;

&lt;p&gt;Besides services and a portfolio, the site doubles as a small toolbox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;10 free browser tools&lt;/strong&gt; – palette &amp;amp; contrast checker, favicon generator, image optimizer, SEO check, CSS/JS/SVG minifiers, a &lt;code&gt;clamp()&lt;/code&gt; calculator, JSON formatter, a TYPO3 Fluid ViewHelper generator and a client-briefing generator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 apps for iPhone &amp;amp; Mac&lt;/strong&gt; – no account, no ads, data stays on the device&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A glossary&lt;/strong&gt; of print, web and video terms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work with TYPO3 or just want a quick contrast check, have a look: 👉 &lt;a href="https://www.stefan-andres.at/tools" rel="noopener noreferrer"&gt;stefan-andres.at/tools&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear what you'd improve – or what moved &lt;em&gt;your&lt;/em&gt; Lighthouse scores the most.&lt;/p&gt;

</description>
      <category>typo3</category>
      <category>webperf</category>
      <category>a11y</category>
      <category>css</category>
    </item>
    <item>
      <title>Hey DEV! I'm Stefan — TYPO3 dev, and I just launched a page of free browser tools</title>
      <dc:creator>stefan andres</dc:creator>
      <pubDate>Mon, 24 Aug 2026 05:33:10 +0000</pubDate>
      <link>https://dev.to/stefan_andres_07/hey-dev-im-stefan-typo3-dev-and-i-just-launched-a-page-of-free-browser-tools-4h65</link>
      <guid>https://dev.to/stefan_andres_07/hey-dev-im-stefan-typo3-dev-and-i-just-launched-a-page-of-free-browser-tools-4h65</guid>
      <description>&lt;p&gt;Hey, I'm Stefan&lt;/p&gt;

&lt;p&gt;I'm a freelance web designer &amp;amp; developer based in Klagenfurt, Austria — though these days most of my work happens for clients well beyond Austria's borders. My focus is &lt;strong&gt;TYPO3&lt;/strong&gt;: building and maintaining sites end-to-end, from branding and visual identity through to a finished, working website. Alongside that I do graphic design and video editing, so a project usually has me moving between code, layout, and media in the same afternoon.&lt;/p&gt;

&lt;p&gt;I'm mostly self-taught on the dev side, which means I've spent a lot of time in the last few years pulling apart how things work under the hood — TypoScript, page performance, and lately the more technical end of SEO (security headers, structured data, Core Web Vitals). This post is partly an excuse to talk about the result of that: a small toolset I just shipped on my site.&lt;/p&gt;

&lt;p&gt;What I built&lt;/p&gt;

&lt;p&gt;I wanted a set of quick, no-nonsense tools I could point clients (and myself) to instead of juggling five different sketchy websites full of ads. So I built my own, all living at &lt;a href="https://www.stefan-andres.at/tools" rel="noopener noreferrer"&gt;stefan-andres.at/tools&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Palette &amp;amp; Contrast Checker&lt;/strong&gt; — pick colors and instantly check contrast ratios for accessibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Icon &amp;amp; Favicon Generator&lt;/strong&gt; — drop in a logo (PNG, SVG, even AI/PDF/EPS) and get every icon size you need, ready to download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Image Optimizer&lt;/strong&gt; — compress images for the web without leaving your browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS Minifier&lt;/strong&gt; and &lt;strong&gt;JS Minifier&lt;/strong&gt; — paste your code, get a minified version back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO &amp;amp; Performance Check&lt;/strong&gt; — enter a URL and get an on-page SEO report plus real Google PageSpeed metrics (LCP, FCP, CLS) with plain-language suggestions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of these run &lt;strong&gt;entirely client-side&lt;/strong&gt; — nothing you upload leaves your browser. Under the hood I'm using libraries like &lt;a href="https://github.com/terser/terser" rel="noopener noreferrer"&gt;Terser&lt;/a&gt; and &lt;a href="https://github.com/css/csso" rel="noopener noreferrer"&gt;csso&lt;/a&gt; for minification and &lt;a href="https://pdf-lib.js.org/" rel="noopener noreferrer"&gt;pdf-lib&lt;/a&gt; / &lt;a href="https://mozilla.github.io/pdf.js/" rel="noopener noreferrer"&gt;pdf.js&lt;/a&gt; to handle vector file rendering for the icon generator. The SEO checker is the one exception — it calls a small PHP backend on my own server, which in turn talks to the Google PageSpeed Insights API.&lt;/p&gt;

&lt;p&gt;What I learned building it&lt;/p&gt;

&lt;p&gt;A few things stuck with me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bilingual UX is more work than it looks.&lt;/strong&gt; The whole tools page runs in English and German, including the &lt;em&gt;dynamically generated&lt;/em&gt; SEO report text — which meant threading a &lt;code&gt;lang&lt;/code&gt; parameter all the way from the front-end fetch call through to the PHP backend's string tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side file processing has come a long way.&lt;/strong&gt; Being able to minify JS/CSS or rasterize a vector PDF entirely in the browser, with no server round-trip, still feels a little magical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security headers and structured data are underrated.&lt;/strong&gt; Once I started running my own SEO tool against my own site, I found (and fixed) my own gaps — missing CSP, missing JSON-LD, missing an apple-touch-icon. Nothing dramatic, just easy wins that add up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say hi&lt;/p&gt;

&lt;p&gt;I'm always happy to talk TYPO3, front-end tooling, or technical SEO — and I'm open to freelance work and collaborations, wherever you're based. Feel free to check out the tools, break them, and tell me what's missing. 🙂&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;
Stefan&lt;/p&gt;

</description>
      <category>freelance</category>
      <category>showdev</category>
      <category>tools</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
