<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Michael Wegener&apos;s Learn &amp; Projects | Blog</title><description/><link>https://michael.wegener.engineering</link><language>de</language><item><title>PageSpeed 100 Quality Gates - Ein Weg zu perfekter Webperformance</title><link>https://michael.wegener.engineering/de/blog/2026-07-11-pagespeed-100-quality-gates</link><guid isPermaLink="true">https://michael.wegener.engineering/de/blog/2026-07-11-pagespeed-100-quality-gates</guid><pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;pagespeed-100-quality-gates&quot;&gt;PageSpeed 100 Quality Gates&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;Wie wir einen Lighthouse-Quality-Gate-Workflow implementiert haben, der jeden Build verweigert, wenn auch nur eine Kategorie unter 100/100 liegt.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;das-ziel&quot;&gt;Das Ziel&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Jede Seite auf michael.wegener.engineering erreicht &lt;strong&gt;100/100&lt;/strong&gt; in allen vier Lighthouse-Kategorien:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kategorie&lt;/th&gt;
&lt;th&gt;Ziel&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;Lighthouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;Lighthouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best Practices&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;Lighthouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;Lighthouse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Das gilt für jede Seite, beide Sprachversionen (EN/DE), und beide Viewports (Desktop/Mobile).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;die-implementation&quot;&gt;Die Implementation&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;lighthouse-ci-integration&quot;&gt;Lighthouse CI Integration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Wir haben &lt;code dir=&quot;auto&quot;&gt;lighthouse&lt;/code&gt; (v13.4.0) und &lt;code dir=&quot;auto&quot;&gt;@lhci/cli&lt;/code&gt; (v0.15.1) als Build-Abhängigkeiten hinzugefügt. Die Konfiguration in &lt;code dir=&quot;auto&quot;&gt;.lighthouserc.json&lt;/code&gt; erzwingt 100/100 als Minimum:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;ci&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;collect&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;staticDir&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;dist&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;settings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;formFactor&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;mobile&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;onlyCategories&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;performance&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;accessibility&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;best-practices&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;seo&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;assert&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;assertions&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;categories:performance&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;span&gt;&quot;minScore&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;}],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;categories:accessibility&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;span&gt;&quot;minScore&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;}],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;categories:best-practices&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;span&gt;&quot;minScore&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;}],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;categories:seo&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;span&gt;&quot;minScore&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;}]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;der-quality-gate-script&quot;&gt;Der Quality-Gate-Script&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Das Script &lt;code dir=&quot;auto&quot;&gt;scripts/quality/validate-pagespeed.sh&lt;/code&gt; prüft jede Seite automatisch nach dem Build:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-euo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pipefail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Audit all pages in both locales&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;locale&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/en&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;PAGES&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# Run Lighthouse, extract scores, assert &gt;= 100&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;audit_page&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$page&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$locale&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Exit 1 if any page fails&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Jeder Build, der eine Seite mit weniger als 100/100 in einer Kategorie produziert, schlägt automatisch fehl.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;parallel-multisprachige-startseite&quot;&gt;Parallel: Multisprachige Startseite&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Gleichzeitig haben wir die Startseite vollständig bilingual umgestellt:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;locale-label-korrektur&quot;&gt;Locale-Label-Korrektur&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Die Root-Sprache war fälschlicherweise als “Deutsch” konfiguriert, obwohl der Inhalt auf Englisch war:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Vorher (falsch)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;locales: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;root: { label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Deutsch&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, lang: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;de: { label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;English&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, lang: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;en&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Nachher (korrekt)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;locales: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;root: { label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;English&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, lang: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;en&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;de: { label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Deutsch&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, lang: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;theme-toggle&quot;&gt;Theme Toggle&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Ein Light/Dark-Mode-Schalter mit localStorage-Persistenz und System-Preference-Fallback:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;theme-toggle&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onclick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;toggleTheme&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sun-icon&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;moon-icon&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;display:none;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initTheme&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;stored&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;localStorage&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;prefersDark&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;matchMedia&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;matches&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;documentElement&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setAttribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;data-theme&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stored&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;prefersDark&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;dark&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;light&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;css-custom-properties-für-themes&quot;&gt;CSS Custom Properties für Themes&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Statt hartcodierte Farben verwenden wir CSS-Variablen, die pro Theme überschrieben werden:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;:root&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--bg-primary&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;f8f9fa&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--text-primary&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;333333&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--card-shadow&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.05&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;data-theme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;dark&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--bg-primary&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;0f0f0f&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--text-primary&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;e4e4e7&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--card-shadow&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.3&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;astro-style-template-expressions&quot;&gt;Astro &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;style&gt;&lt;/code&gt; Template-Expressions&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Astro unterstützt &lt;strong&gt;keine&lt;/strong&gt; Template-Expressions in &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;style&gt;&lt;/code&gt;-Blöcken. Das bricht Hero-Hintergrundbilder:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;!-- BROKEN: Template expressions don&apos;t work in &amp;#x3C;style&gt; --&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.hero&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;background-image&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;${image.src}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;!-- WORKING: Inline style object on element --&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;section&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;--hero-bg-image&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;url(&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;seite-generierte-i18n&quot;&gt;Seite-generierte i18n&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Da Astro-Seiten (nicht Starlight) keinen Zugang zur i18n-Collections-API haben, verwenden wir ein simples Translation-Objekt mit &lt;code dir=&quot;auto&quot;&gt;Astro.currentLocale&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const locale = Astro.currentLocale ?? &apos;en&apos;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const t = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;en: { title: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Hello&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, description: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Welcome&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;de: { title: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Hallo&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, description: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Willkommen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;[locale] ?? t.en;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;ergebnisse&quot;&gt;Ergebnisse&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;41 Dateien&lt;/strong&gt; mit vollständigen EN-Übersetzungen&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quality Gate&lt;/strong&gt; prüft jeden Build automatisch&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theme Toggle&lt;/strong&gt; mit Persistence und System-Preference-Support&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Landing Page&lt;/strong&gt; vollständig bilingual (DE/EN)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx Cache Headers&lt;/strong&gt; für statische Assets (1 Jahr TTL)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;server-side-nginx-cache-headers-auf-synology-dsm&quot;&gt;Server-Side: Nginx Cache Headers auf Synology DSM&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Der letzte Schritt zum perfekten Score ist die serverseitige Konfiguration der Cache-Header. Synology DSM verwendet einen eigenen Nginx als Reverse Proxy. Die Konfiguration lebt in zwei Schichten:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;schicht-1-site-definition-uuid-basiert&quot;&gt;Schicht 1: Site-Definition (UUID-basiert)&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/usr/local/etc/nginx/conf.d-available/&amp;#x3C;uuid&gt;.w3conf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Diese Datei definiert Document Root und Index-Dateien für jede Web Station:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;root &lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;&quot;/volume1/web/michael-wegener-engineering&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;index &lt;/span&gt;&lt;span&gt;  index.htm index.html;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;include &lt;/span&gt;&lt;span&gt;/usr/local/etc/nginx/conf.d/&amp;#x3C;uuid&gt;/user.conf*;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;schicht-2-user-config-beständig&quot;&gt;Schicht 2: User Config (beständig)&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Die &lt;code dir=&quot;auto&quot;&gt;user.conf&lt;/code&gt; wird von DSM nicht überschrieben und ist der richtige Ort für Custom-Header:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/usr/local/etc/nginx/conf.d/&amp;#x3C;uuid&gt;/user.conf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Security Headers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;X-Content-Type-Options &lt;/span&gt;&lt;span&gt;&apos;nosniff&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;X-Frame-Options &lt;/span&gt;&lt;span&gt;&apos;SAMEORIGIN&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;X-XSS-Protection &lt;/span&gt;&lt;span&gt;&apos;1; mode=block&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;Referrer-Policy &lt;/span&gt;&lt;span&gt;&apos;strict-origin-when-cross-origin&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;Permissions-Policy &lt;/span&gt;&lt;span&gt;&apos;geolocation=(), microphone=(), camera=(), payment=(), usb=()&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Cache headers für statische Assets (1 Jahr)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Hashed Assets können aggressiv gecacht werden,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# da sich die Dateinamen bei Updates ändern&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;location&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\.(webp|avif|png|jpe?g|gif|ico|svg|css|js|woff2|ttf|otf|eot)$ &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;expires &lt;/span&gt;&lt;span&gt;365d&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;Cache-Control &lt;/span&gt;&lt;span&gt;&quot;public, max-age=31536000, immutable&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;X-Content-Type-Options &lt;/span&gt;&lt;span&gt;&apos;nosniff&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;X-Frame-Options &lt;/span&gt;&lt;span&gt;&apos;SAMEORIGIN&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;X-XSS-Protection &lt;/span&gt;&lt;span&gt;&apos;1; mode=block&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;add_header &lt;/span&gt;&lt;span&gt;Referrer-Policy &lt;/span&gt;&lt;span&gt;&apos;strict-origin-when-cross-origin&apos;&lt;/span&gt;&lt;span&gt; always;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;warum-location-block-für-security-header&quot;&gt;Warum Location-Block für Security-Header?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Nginx-Location-Blöcke erben &lt;strong&gt;keine&lt;/strong&gt; Parent-Header automatisch. Jeder Location-Block muss Security-Header wiederholen, wenn er eigene &lt;code dir=&quot;auto&quot;&gt;add_header&lt;/code&gt;-Direktiven enthält.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;deploy-schritt&quot;&gt;Deploy-Schritt&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# SSH zum Synology NAS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;michael@ds718&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Config updaten&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/tmp/user.conf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/usr/local/etc/nginx/conf.d/&amp;#x3C;uuid&gt;/user.conf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Syntax prüfen&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nginx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-t&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Reload (kein Downtime)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nginx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reload&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;verifikation&quot;&gt;Verifikation&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-sI&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://michael.wegener.engineering/_astro/image.webp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# cache-control: max-age=31536000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# cache-control: public, max-age=31536000, immutable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;finale-scores&quot;&gt;Finale Scores&lt;/h2&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kategorie&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best Practices&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Die 99% Performance-Punktzahl ist das praktische Maximum für selbstgehostete Sites. Der letzte Prozentpunkt wird von TTFB (Time to First Byte) begrenzt, das von der Server-Hardware und Netzwerk-Latenz abhängt.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;code&quot;&gt;Code&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Alle Änderungen sind unter Version 1.3.3 verfügbar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://git.satware.ai/mw/learn/pulls/73&quot;&gt;PR #73&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git.satware.ai/mw/learn/tree/HEAD/site/scripts/quality/validate-pagespeed.sh&quot;&gt;Quality Gate Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git.satware.ai/mw/learn/tree/HEAD/site/.lighthouserc.json&quot;&gt;Lighthouse Config&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>pagespeed</category><category>quality-gates</category><category>lighthouse</category><category>performance</category><category>i18n</category></item><item><title>Neovim als Standard-Editor — Aufbau eines modernen Workflows</title><link>https://michael.wegener.engineering/de/blog/2026-07-07-neovim-als-standard-editor</link><guid isPermaLink="true">https://michael.wegener.engineering/de/blog/2026-07-07-neovim-als-standard-editor</guid><pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;neovim-als-standard-editor--aufbau-eines-modernen-workflows&quot;&gt;Neovim als Standard-Editor — Aufbau eines modernen Workflows&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;Heute habe ich Neovim 0.12 auf meinem Manjaro-System von einem Minimal-Setup zu einem vollständigen Developer-Workflow aufgebaut. Keine Distribution wie LazyVim oder NvChad — alles selbst konfiguriert, modular in Lua, mit modernsten Plugins.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;der-ausgangspunkt&quot;&gt;Der Ausgangspunkt&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Mein &lt;code dir=&quot;auto&quot;&gt;init.lua&lt;/code&gt; hatte genau drei Zeilen:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.o.grepprg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;rg --vimgrep&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.o.grepformat &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;%f:%l:%c:%m&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ripgrep als Suchmaschine — das wars. Kein Plugin-Manager, kein LSP, keine Syntax-Highlighting-Plugins, kein Farbschema. Ein blanker Schlssel.&lt;/p&gt;
&lt;p&gt;Interessant: Neovim 0.12.3 ist auf Manjaro aktuell und bringt schon einiges Built-in — Lua-First-Konfiguration, integriertes LSP-Client-Framework, und einen neuen &lt;code dir=&quot;auto&quot;&gt;vim.lsp.config&lt;/code&gt; API, der das alte &lt;code dir=&quot;auto&quot;&gt;lspconfig&lt;/code&gt;-Muster ablöst.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;das-fundament-lazynvim-und-modulare-lua-konfiguration&quot;&gt;Das Fundament: lazy.nvim und modulare Lua-Konfiguration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ich habe mich für lazy.nvim als Plugin-Manager entschieden. Der große Vorteil: Plugins werden nur geladen, wenn sie benötigt werden — ob per Event (&lt;code dir=&quot;auto&quot;&gt;BufReadPost&lt;/code&gt;), per Tastenkombination, oder beim Start. Das hält die Startup-Zeit niedrig.&lt;/p&gt;
&lt;p&gt;Die Konfiguration ist modular in &lt;code dir=&quot;auto&quot;&gt;lua/&lt;/code&gt; gegliedert:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;~/.config/nvim/lua/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── core/init.lua      # Globale Einstellungen&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── core/keymaps.lua   # Tastenkombinationen&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── plugins/           # Plugin-Spezifikationen&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── lsp/init.lua       # LSP-Konfiguration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Jedes Plugin ist eine einzelne Lua-Datei, die eine lazy.nvim-Spezifikation zurückgibt. Klar, überschaubar, leicht zu erweitern.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;lsp-statt-syntax-highlighting&quot;&gt;LSP statt Syntax-Highlighting&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Der klassische Weg — Syntax-Dateien für jedes Filetype — ist tot. Stattdessen Language Server Protocol: der gleiche Server, der in VS Code läuft, liefert jetzt auch in Neovim Code-Vervollständigung, Go-to-Definition, Refactoring, und Diagnostics.&lt;/p&gt;
&lt;p&gt;Was mich überrascht hat: Neovim 0.12 hat &lt;code dir=&quot;auto&quot;&gt;vim.lsp.config&lt;/code&gt; als neuen Weg, LSP-Server zu konfigurieren. Die alte &lt;code dir=&quot;auto&quot;&gt;nvim-lspconfig&lt;/code&gt; ist jetzt ein Deprecated-“Framework”. So geht es modern:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.lsp.config[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;pyright&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;capabilities&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;capabilities&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;on_attach&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;on_attach&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.lsp.&lt;/span&gt;&lt;span&gt;enable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;pyright&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Achtung: Die LSP-Server selbst werden von Neovim nicht mitgeliefert. Ich habe über &lt;code dir=&quot;auto&quot;&gt;pacman&lt;/code&gt; installiert: pyright, ruff, typescript-language-server, lua-language-server, bash-language-server, yaml-language-server, tailwindcss-language-server, eslint-language-server. Acht Server, acht Sprachen abgedeckt.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;autocompletion-mit-blinkcmp&quot;&gt;Autocompletion mit blink.cmp&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Hier gab es eine echte Lernkurve. Das klassische &lt;code dir=&quot;auto&quot;&gt;nvim-cmp&lt;/code&gt; wird langsam von &lt;code dir=&quot;auto&quot;&gt;blink.cmp&lt;/code&gt; abgelöst — ein komplett in Rust geschriebener Fuzzy-Matcher, der deutlich schneller ist und Tippfehler toleriert.&lt;/p&gt;
&lt;p&gt;Die Installation über lazy.nvim mit einem Release-Tag lädt vorgebaute Binaries — kein Rust-Toolchain nötig:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;saghen/blink.cmp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;rafamadriz/friendly-snippets&lt;/span&gt;&lt;span&gt;&quot; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1.*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;keymap&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;preset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&quot; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;sources&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;lsp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;snippets&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt;&quot; &lt;/span&gt;&lt;span&gt;} },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Die Erfahrung: &lt;code dir=&quot;auto&quot;&gt;Ctrl+Space&lt;/code&gt; öffnet das Menü, &lt;code dir=&quot;auto&quot;&gt;Ctrl+y&lt;/code&gt; akzeptiert. LSP-Vervollständigungen sind sofort da, Pfade werden vorgeschlagen, Snippets sind dabei. Fühlt sich an wie VS Code, nur ohne den Electron-Overhead.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;fuzzy-finding-mit-telescope&quot;&gt;Fuzzy Finding mit Telescope&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Telescope.nvim ist der File-Finder und Live-Grep von Neovim. &lt;code dir=&quot;auto&quot;&gt;Space f f&lt;/code&gt; durchsucht alle Dateien im Projekt, &lt;code dir=&quot;auto&quot;&gt;Space f g&lt;/code&gt; grept live mit ripgrep. Der FZF-Extension bringt noch besseres Ranking.&lt;/p&gt;
&lt;p&gt;Was ich mag: Der Preview zeigt den Dateiinhalt rechts neben der Ergebnisliste. Man sieht den Kontext, bevor man die Datei öffnet. Und die &lt;code dir=&quot;auto&quot;&gt;ripgrep&lt;/code&gt;-Integration ist nahtlos — genau die gleichen Suchen wie auf der Kommandozeile.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;git-integration-mit-gitsigns&quot;&gt;Git-Integration mit gitsigns&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Gitsigns zeigt die Git-Changes direkt in der Sign-Spalte — &lt;code dir=&quot;auto&quot;&gt;+&lt;/code&gt; für hinzugefügt, &lt;code dir=&quot;auto&quot;&gt;~&lt;/code&gt; für geändert, &lt;code dir=&quot;auto&quot;&gt;_&lt;/code&gt; für gelöscht. Mit &lt;code dir=&quot;auto&quot;&gt;Space g p&lt;/code&gt; und &lt;code dir=&quot;auto&quot;&gt;Space g n&lt;/code&gt; springt man von Hunk zu Hunk. &lt;code dir=&quot;auto&quot;&gt;Space g s&lt;/code&gt; staged einen Hunk, &lt;code dir=&quot;auto&quot;&gt;Space g r&lt;/code&gt; resetet ihn.&lt;/p&gt;
&lt;p&gt;Alles visuell, alles im Editor. Kein &lt;code dir=&quot;auto&quot;&gt;git diff&lt;/code&gt; im Terminal mehr nötig.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;was-als-nächstes-kommt&quot;&gt;Was als Nächstes kommt&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Einige Dinge, die auf der Liste stehen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;telescope-fzf-native&lt;/code&gt; für noch besseres Fuzzy-Ranking (Rust-Backend statt Lua)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;nvim-navic&lt;/code&gt; für LSP-basierte Navigation in der Statusleiste&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;oil.nvim&lt;/code&gt; als Dateibrowser, der den Dateisystem-Inhalt direkt als Buffer darstellt&lt;/li&gt;
&lt;li&gt;Eigene Snippets für wiederkehrende Patterns (PHP-Controller, Python-Test-Klassen)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;mason.nvim&lt;/code&gt; für automatisches LSP-Server-Management (statt pacman-Pakete)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nächstes: Die &lt;code dir=&quot;auto&quot;&gt;telescope&lt;/code&gt;-Erweiterungen ausprobieren und entscheiden, ob &lt;code dir=&quot;auto&quot;&gt;oil.nvim&lt;/code&gt; den &lt;code dir=&quot;auto&quot;&gt;nvim-tree&lt;/code&gt; ersetzen soll — oder ob beides seinen Platz hat.&lt;/p&gt;
&lt;p&gt;Ein begleitender Cheat Sheet fasst die wichtigsten Tastenkürzel zusammen: &lt;a href=&quot;https://michael.wegener.engineering/cheatsheets/neovim-cheatsheet&quot;&gt;Neovim Cheat Sheet&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>Neovim</category><category>lazy.nvim</category><category>LSP</category><category>blink.cmp</category><category>Telescope</category><category>Developer-Setup</category><category>Treesitter</category></item><item><title>Stable Diffusion - Bilder generieren mit FLUX.2-klein-4B</title><link>https://michael.wegener.engineering/de/blog/2026-07-06-stable-diffusion-bilder-generieren</link><guid isPermaLink="true">https://michael.wegener.engineering/de/blog/2026-07-06-stable-diffusion-bilder-generieren</guid><pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;stable-diffusion---bilder-generieren-mit-flux2-klein-4b&quot;&gt;Stable Diffusion - Bilder generieren mit FLUX.2-klein-4B&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;Heute habe ich erste Bilder mit dem FLUX.2-klein-4B-Modell generiert. Fünf Versuche, fünf verschiedene Stilrichtungen - von Natur über Cyberpunk bis kulinarische Fotografie.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;die-ergebnisse&quot;&gt;Die Ergebnisse&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;natur---landschaft&quot;&gt;Natur - Landschaft&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Eine Landschaftsaufnahme in warmen Sonnenuntergongfarben.&lt;/p&gt;
&lt;img src=&quot;https://michael.wegener.engineering/_astro/1-nature-landscape.51SGon9e_2putX.webp&quot; alt=&quot;Nature Landscape&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;512&quot; height=&quot;512&quot;&gt;
&lt;div&gt;&lt;h3 id=&quot;scifi---cyberpunk&quot;&gt;SciFi - Cyberpunk&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Ein Cyberpunk-Szenario mit Neonlichtern und futuristischer Architektur.&lt;/p&gt;
&lt;img src=&quot;https://michael.wegener.engineering/_astro/2-scifi-cyberpunk.JjhCW2vR_Z1taUln.webp&quot; alt=&quot;SciFi Cyberpunk&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;512&quot; height=&quot;512&quot;&gt;
&lt;div&gt;&lt;h3 id=&quot;portrait---realistisch&quot;&gt;Portrait - Realistisch&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Ein realistisches Porträt mit natürlicher Beleuchtung.&lt;/p&gt;
&lt;img src=&quot;https://michael.wegener.engineering/_astro/3-portrait-realistic.BWz-0tfI_m5CB4.webp&quot; alt=&quot;Portrait Realistic&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;512&quot; height=&quot;512&quot;&gt;
&lt;div&gt;&lt;h3 id=&quot;abstract---artistisch&quot;&gt;Abstract - Artistisch&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Ein abstraktes, künstlerisches Bild mit fließenden Formen.&lt;/p&gt;
&lt;img src=&quot;https://michael.wegener.engineering/_astro/4-abstract-artistic.Cgf-1GlJ_1ATgsX.webp&quot; alt=&quot;Abstract Artistic&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;512&quot; height=&quot;512&quot;&gt;
&lt;div&gt;&lt;h3 id=&quot;food---kulinarisch&quot;&gt;Food - Kulinarisch&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Ein appetitliches Food-Foto mit natürlichen Farben und Texturen.&lt;/p&gt;
&lt;img src=&quot;https://michael.wegener.engineering/_astro/5-food-culinary.BWr7H010_Z20apFE.webp&quot; alt=&quot;Food Culinary&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;512&quot; height=&quot;512&quot;&gt;
&lt;div&gt;&lt;h2 id=&quot;erste-eindrücke&quot;&gt;Erste Eindrücke&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;FLUX.2-klein-4B liefert bei 4 Milliarden Parametern beachtliche Ergebnisse für ein Modell seiner Größe. Die Prompt-Interpretation ist direkt, die Farbtreue gut, und die Texturwiedergabe überzeugt - besonders bei den Food- und Portrait-Bildern.&lt;/p&gt;
&lt;p&gt;Interessant: Das Modell neigt dazu, prompts wörtlich zu nehmen. Bei abstrakteren Konzepten (wie “Cyberpunk”) zeigt es sich, dass mehr Prompt-Engineering nötig ist, als man auf den ersten Blick denken würde.&lt;/p&gt;
&lt;p&gt;Nächstes: Verschiedene Samplerverfahren vergleichen (Euler, DPM++) und prüfen, ob sich das größere FLUX.1-dev-Modell lohnt.&lt;/p&gt;</content:encoded><category>stable-diffusion</category><category>flux</category><category>bildgenerierung</category><category>ai</category></item></channel></rss>