← Zur Übersicht

Wie diese Website gebaut, getestet und veröffentlicht wird

3. Mai 2026

Diese Seite ist bewusst einfach gehalten: eine statisch generierte Website ohne Backend und ohne Client-seitiges JavaScript (abgesehen von den üblichen Drittanbieter-Skripten wie Google Tag Manager und Cookiebot). Hier ist ein kurzer Überblick, wie das Ganze aufgebaut ist.

Templating: 11ty mit Nunjucks

Die Seiten sind einfache HTML-Dateien, die ein gemeinsames Layout erweitern. 11ty (Eleventy) übernimmt das Rendering, Nunjucks liefert die Template-Syntax:

{% extends "layout.html" %}
{% block title %}Blog – Felix Boers{% endblock %}
{% block content %}
<section>...</section>
{% endblock %}

Das Layout definiert Blöcke für Titel, Meta-Tags, Open-Graph-Daten und den eigentlichen Inhalt, sodass jede Seite nur überschreibt, was sie wirklich braucht.

Styling: Tailwind v4 ganz ohne Config-Datei

Seit Version 4 konfiguriert man Tailwind direkt in CSS statt in einer tailwind.config.js. Die Design-Tokens dieser Seite (Farben, Schriftart) stehen in einem @theme-Block:

@theme {
  --color-accent: #4a9edd;
  --color-surface: #161f2e;
  --color-base: #0d1117;
  --font-sans: "Montserrat", ui-sans-serif, system-ui, sans-serif;
}

Diese Tokens tauchen dann als normale Utility-Klassen auf, z. B. text-accent oder bg-surface.

Build: 11ty und Tailwind CLI Hand in Hand

Der Build besteht aus zwei unabhängigen Schritten – Tailwind kompiliert das CSS, 11ty rendert die Seiten – beide schreiben in dasselbe dist/-Verzeichnis:

"build": "rm -rf dist && npm run build:css && npm run build:eleventy",
"build:css": "tailwindcss -i src/styles/main.css -o dist/styles/main.css --minify",
"build:eleventy": "eleventy"

Tests: Playwright und ein selbstgebauter Kontrast-Check

Die Test-Suite läuft mit Playwright gegen einen echten Produktions-Build. Neben Navigation, Links und Inhalten prüft ein selbstgeschriebener WCAG-Kontrast-Test jeden sichtbaren Textknoten gegen seinen tatsächlichen Hintergrund:

function contrast(fg, bg) {
  const l1 = relLuminance(fg);
  const l2 = relLuminance(bg);
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  return (lighter + 0.05) / (darker + 0.05);
}

So fällt sofort auf, wenn eine neue Farbe (zum Beispiel für das Syntax-Highlighting in diesem Blog) nicht genug Kontrast zum Hintergrund hat.

Deployment: Docker, Gitea Actions und semantic-release

Ein mehrstufiges Dockerfile baut die Seite und liefert sie über nginx aus:

FROM node:24-alpine AS build
WORKDIR /app
COPY . .
RUN npm run build

FROM nginx
COPY --from=build /app/dist /usr/share/nginx/html

Jeder Push auf main läuft durch Tests und Build; basierend auf Conventional Commits ermittelt semantic-release die nächste Version und setzt bei Bedarf einen Git-Tag. Ein Tag-Push löst den Deploy-Workflow aus: Docker-Image bauen, in eine eigene Registry pushen, per SSH auf dem Server den Container neu starten. Ein vorgeschalteter Caddy-Reverse-Proxy kümmert sich um TLS und leitet Anfragen an den Container weiter.

Am Ende ist es ein sehr kleines, aber vollständiges System – und genau der richtige Rahmen, um jetzt auch regelmäßiger zu bloggen.