HUGO, Tailwind & TypeScript

Die Tools hinter Skorar.dev

HUGO, Tailwind & TypeScript

Die Tools hinter Skorar.dev

19. Juni 2025 13:43 (Letzte Änderung: 19. Juni 2025 20:31) |
Ein Bild von Skorar
Skorar
| 11 Minuten   | 2211 Wörter

Tags: hugo markdown tailwind typescript

Kategorien: webdevelopment tools

Ich dachte, heute sprechen wir mal über die Frameworks und Tools, welche diese Webseite antreiben.
Wir schauen uns einmal an, wie ich auf die Idee gekommen bin, genau diese Frameworks zu nutzen und welche Vorteile sie mir bieten.

Intro

Die Tools hinter Skorar.dev
Die Tools hinter Skorar.dev | Raphael Sundermann: https://skorar.com
Die Webseite wurde mit den folgenden Tools & Frameworks erstellt:

Das war es im Grunde auch schon. Aus mehr besteht die Webseite nicht. Gut, ausgenommen natürlich der GitLab CI/CD Pipeline, die dafür sorgt, dass die Webseite bei jedem Push automatisch neu gebaut und deployt wird. Aber darum soll es heute gar nicht gehen. Und natürlich noch kleineren Tools wie pagefind für die Suche und Hamburger für das Hamburger Menü. Aber das sind eher kleinere Tools, die nicht wirklich einen eigenen Abschnitt benötigen.

Dann schauen wir uns die einzelnen Tools & Frameworks einmal genauer an.

HUGO

HUGO Logo
HUGO Logo | HUGO: https://gohugo.io

HUGO ist ein sogenannter Static Site Generator (SSG). Das bedeutet, dass die Webseite im Vorfeld generiert wird und auf meinem Server nur noch statische Dateien liegen. Das hat natürlich den Vorteil, dass die Webseite, auch ohne erweiterte Optimierung, zügig lädt.

Im Vergleich zu einer typischen Webseite, welche z.B. mit einem CMS wie WordPress erstellt wurde, ist die Ladezeit um ein Vielfaches kürzer.
Was natürlich aber auch damit zu tun hat, ob man seine Webseite optimiert oder nicht, was aber ein anderes Thema ist.

Das, was HUGO für mich besonders interessant gemacht hat, ist einfach, dass ich sowohl meine BLOG-Posts, als auch meine allgemeinen Seiten in Markdown schreiben kann. Was für mich das Beste aus einem CMS und einer statischen, HTML-basierten Webseite vereint. Denn ich muss nicht für jeden Blog-Post eine neue HTML-Seite erstellen, sondern kann einfach Markdown-Dateien schreiben und HUGO generiert mir daraus die Entsprechende HTML-Seiten. Gleichzeitig habe ich kein umfangreiches System im Hintergrund laufen, welches z.B. die Performance der Webseite negativ beeinflussen könnte.

Einstieg in HUGO

Der Einstieg war für mich zwar keine riesige Herausforderung, aber ich musste mich dennoch erst einmal in die Struktur und die Funktionsweise von HUGO einarbeiten.
Ich bin Template-Engine wie z.B. Twig gewohnt, da ich in meinem Beruf viel mit PHP respektive Symfony arbeite. Aber ich muss zugeben, dass ich nicht gerade der stärkste Frontend-Entwickler bin. Ich finde mich zurecht, aber bleibe doch lieber im Backend 😄.

Grundsätzlich lässt sich aber sagen, dass HUGO nach einer initialen Einarbeitung recht einfach in der Handhabung ist.
Das Templating läuft über Go-Templates, was für mich als PHP-Entwickler nicht allzu fremd ist. Hier z.B. wie man einen Block in einer übergeordneten Datei, z.B. der baseof.html, definiert und in einer Unterseite, z.B. single.html, erweitert:

baseof.html:

{{ define "main" }}
  <main>
    {{ block "content" . }}{{ end }}
  </main>
{{ end }}

single.html:

{{ define "content" }}
  <article>
    <h1>{{ .Title }}</h1>
    <p>{{ .Content }}</p>
  </article>
{{ end }}

Das sollte gerade Entwickler:innen, die mit z.B. Twig vertraut sind, nicht allzu fremd vorkommen. Wobei sich sagen lässt, dass HUGO auch die ein oder andere Eigenheit hat. So ist das mit den Parametern, welche man von einem Post an ein Template übergibt, anders, als wenn man dies z.B. mit einer Seite macht.

Eventuell war ich auch gerade etwas vorschnell. Wenn man keine Erfahrung mit HUGO hat, könnte das eventuell verwirrend sein, was ich mit der Übergabe von Parametern meine.

Jede .md-Datei in Hugo hat die gleiche Struktur. Der “Header”-Block, welcher Metadaten, Parameter und weitere Informationen enthält. Darunter findet sich dann der eigentliche Inhalt.

Hier mal ein Beispiel der Md-Datei, welche für diesen Blog-Post genutzt wird, während ich ihn entwerfe:

---
title: "Draft-01"
summary: "Summary"
slug: "zwischen-zeilen-und-eindruck.md"
date: 2025-06-18T11:39:00+02:00
updated: 
tags: ["hugo", "markdown"]
kategorien: ["webdevelopment"]

draft: false

author: "Skorar"

hero_image: "/images/blog.jpg"
---

Wir finden dort jetzt einiges, was wir erwarten können. Etwa den Titel, das Datum, die Tags und Kategorien. Aber eben auch eigene Parameter wie hero_image, welche ich dann in meinem Template nutzen kann, um z.B. ein Bild im Header anzuzeigen.

Das sind ehrlicherweise auch die wichtigsten Dinge, die man über HUGO wissen muss, um zu entscheiden, ob es für einen selbst geeignet ist oder nicht. Alles andere würden den Rahmen dieses Blog-Posts sprengen.

Tailwind CSS

Tailwind CSS Logo
Tailwind CSS Logo | Tailwind CSS: https://tailwindcss.com

Tailwind ist ein CSS-Framework, welches auf Utility-Klassen basiert. Das bedeutet, dass man Klassen nutzt, die jeweils eine bestimmte Eigenschaft oder ein bestimmtes Verhalten definieren. Man schreibt also nicht mehr 100 Klassen, die irgendwo fast identische Dinge machen, sondern baut die von Tailwind bereitgestellten Klassen direkt in die HTML-Elemente ein und im Build Prozess wird dann das CSS generiert, welches diese Klassen definiert.

So sieht z.B. ein einfaches HTML-Element mit Tailwind CSS aus, wenn man z.B. einen Button erstellen möchte:

<button class="bg-blue-500 text-white font-bold py-2 px-4 rounded">
  Klick mich!
</button>

Ich muss zugeben, dass ich mich lange Zeit von Systemen wie Tailwind oder auch z.B. TypeScript ferngehalten habe. Warum nutzen wir etwas Neues, wenn die altbewährten Dinge doch funktionieren? Natürlich kann man bestimmt was dazu lernen, wenn man Tailwind nutzt, aber ich habe doch plain CSS. Reicht das nicht?

Leider glaube ich, die ehrlichste Antwort darauf ist: Nein, es reicht nicht. Nicht weil man eine Webseite nicht mit plain CSS erstellen kann, sondern weil man irgendwann den Punkt erreichen sollte, an dem es mehr um die effiziente Nutzung von Systemen geht, als darum ein elitäres Gefühl der Überlegenheit zu empfinden, weil man sich gegen “diesen Neumodernen Quatsch” auflehnt und lieber “den guten Alten Standard” nutzt, weil all dieser neue Kram ja auch “total unperformant und bloated ist”.

Ja, das gerade waren eventuell Gedächtniszitate von mir, aber auch von alten Kollegen, mit denen ich in der Vergangenheit zusammengearbeitet habe. Ich hatte wirklich eine Phase, in der ich unbedingt alles von der kleinstmöglichen Basis aus lernen wollte. Wäre das weiter fortgeschritten, hätte ich vermutlich irgendwann gelernt, in Binär zu programmieren, weil das ja “am performantesten” ist. Natürlich ist das Quatsch. Klar, irgendwo ist der Ursprung der Softwareentwicklung wichtig, aber wem ist denn damit geholfen, wenn man sich nicht fortbildet und nicht bereit ist, neue Systeme zu lernen?

Gut, jetzt habe ich mich natürlich darum gedrückt, zu erklären, warum ich mich für Tailwind entschieden habe. Also, warum Tailwind? Eigentlich relativ einfach. Ich wollte etwas Neues lernen und wollte Tailwind schon lange mal ausprobieren.

Einstieg in Tailwind

Wie gerade schon erwähnt, arbeitet Tailwind mit Utility-Klassen. Tatsächlich wusste ich das nicht, als ich Tailwind in dem Projekt installiert habe. Was unter anderem daran liegt, dass ich das gesamte Projekt Setup, bzw. die Definition, was ich nutzen will, nicht selbst gemacht habe, sondern ich mit ChatGPT zusammengearbeitet habe, um das Projekt aufzusetzen.

Irgendwo ist die Verwendung von Generative AI etwas umstritten, doch ich muss zugeben, für mich ist AI inzwischen eine verbesserte Form von Google + StackOverflow. Und ich glaube, dort findet sich auch der Konsens bei vielen Entwickler:innen.

Ich muss zugeben, der Einstieg war für mich auch irgendwo ziemlich frustrierend. Gut, Personen, die mich kennen, wissen, dass ich manchmal einen etwas kurzen Geduldsfaden habe, wenn es um Frameworks oder Tools geht, die ich bisher nicht verwendet habe.
Denn ich wusste, wie vorhin erwähnt, nicht, dass Tailwind mit Utility-Klassen arbeitet. Sprich, ich habe verzweifelt versucht zu verstehen, wie ich Standard CSS nutzen soll. Erst mit einer ausführlicheren Erklärung von ChatGPT habe ich verstanden, dass ich die Klassen direkt in die HTML-Elemente einfügen soll und Tailwind dann im Build-Prozess das CSS generiert.

Was für sich auch kein Problem war. Doof war dann nur herauszufinden, wie ich eigene Klassen definiere und wie ich z.B. CSS überschreiben kann. Am Ende ist das dann eine Kombination aus der Tailwind Konfiguration und der Nutzung von @apply in den CSS-Dateien.

So sieht z.B. eine einfache Tailwind Konfiguration aus:

module.exports = {
  content: [
    "./layouts/**/*.html",
    "./assets/**/*.{ts,js}"
  ],
  theme: {
    extend: {
      colors: {
        text: '#e0e0e0',
        brand: {
          DEFAULT: "#1c1c1e",
          dark_gray: "#222324",
          orange: "#ff7043",
          peach:   "#ffb347",
          gold:    "#fffe47",
          code:    "#263238",
        }
      },
    }
  },
  plugins: [
    require('@tailwindcss/typography'),
  ]
}

Und um z.B. das Hamburger Menü anzupassen, welches aus dem Paket Hamburgers von Jonathan Suh stammt, nutze ich folgendes CSS:

/* ===== CUSTOM STYLES FOR HAMBURGER MENU ===== */
.hamburger-inner, .hamburger-inner:before, .hamburger-inner:after,
.hamburger--squeeze .hamburger-inner, .hamburger--squeeze .hamburger-inner:before, .hamburger--squeeze .hamburger-inner:after,
.hamburger.is-active .hamburger-inner, .hamburger.is-active .hamburger-inner:before, .hamburger.is-active .hamburger-inner:after {
    @apply bg-brand-peach;
}

.hamburger .hamburger--squeeze {
    display: none;
}

Das ist natürlich nur ein kleiner Einblick in die Konfiguration von Tailwind, aber ich denke, das sollte reichen, um zu verstehen warum ich mich für Tailwind entschieden habe und wie ich es nutze.

TypeScript

TypeScript Logo
TypeScript Logo | TypeScript: https://www.typescriptlang.org

TypeScript ist eine Programmiersprache, die auf JavaScript basiert und statische Typisierung bietet. Das bedeutet, dass wir in TypeScript Typen für Variablen, Funktionen und Objekte definieren können. Was besonders angenehm ist, wenn man z.B. Erfahrung mit statisch typisierten Programmiersprachen hat, wie z.B. C# oder Java.

Ich gebe zu, dass ich auch ein klein wenig Erfahrung in TypeScript hatte, da ich es damals für die Entwicklung eines GTA-Roleplay-Servers genutzt habe. Das ist zwar nicht unbedingt die glorreichste Stunde meiner Entwicklungsreise, aber ich habe zumindest ein wenig Erfahrung mit TypeScript sammeln können…

Einstieg in TypeScript

Der Einstieg in TypeScript war für mich relativ einfach, da ich bereits Erfahrung mit JavaScript und TypeScript selbst hatte. Ich habe mich also relativ schnell in die Syntax und die Funktionsweise von TypeScript eingearbeitet. Und mich aktiv dazu entschieden, keinen sauberen Code zu schreiben, sondern eine Frankenstein-ähnliche Struktur aus document.addEventListener in der main.ts und components zu nutzen.

Ich möchte hier nicht vorgeben, als wäre diese gesamte Seite super sauber aufgebaut. Teilweise ging es mir vor allem darum, dass es funktioniert. Nichtsdestotrotz möchte ich das Ein oder andere noch zu der Verwendung von TypeScript sagen.

TypeScript, ähnlich wie Tailwind, wird nicht einfach so in den Browser geladen, sondern muss erst einmal kompiliert werden. In meinem Fall nutze ich dafür Vite als Build-Tool. So kann ich mit meiner Konfiguration einfach nur npm run build ausführen und Vite kümmert sich um den Rest (oder halt im DEV dann npm run dev).

Zu der Konfiguration kann ich nicht allzu viel sagen:

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["assets/**/*"]
}

Wie ihr sehen könnt, ist die Konfiguration relativ einfach gehalten. Im Grunde soll sie nur dafür sorgen, dass TypeScript korrekt kompiliert wird und die richtigen Dateien berücksichtigt werden.

Auch die Vite Konfiguration ist relativ einfach gehalten:

import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
    plugins: [
        tailwindcss(),
    ],
    root: '.',
    build: {
        outDir: 'static/build',
        emptyOutDir: true,
        rollupOptions: {
            input: './assets/main.ts',
            output: {
                entryFileNames: 'main.js',
                assetFileNames: 'style.css'
            },
            external: [
                '/pagefind/pagefind.js'
            ]
        }
    }
})

Was mich am Anfang etwas verwirrt hat, ist, dass wir in Vite nur Tailwind drin haben, aber nicht TypeScript. Aber das liegt daran, dass Vite automatisch TypeScript-Dateien und entsprechend behandelt. Musste ich aber auch erst lernen 😄.

Honorable Mentions

Natürlich gibt es noch ein paar andere Tools, die ich nutze, aber die sind eher kleinere Helferlein, die nicht wirklich einen eigenen Abschnitt benötigen. So nutze ich z.B. Hamburgers für das Hamburger Menü und Pagefind für die Suche.

Und auch zu der Nutzung von AI möchte ich noch etwas sagen. Ich habe, wie bereits erwähnt, ChatGPT genutzt, um das Projekt aufzusetzen und um mir bei der Konfiguration von HUGO und Tailwind zu helfen. Teilweise bin ich auch auf Claude umgestiegen, da ich dort das Gefühl hatte, dass ich bessere Antworten bekomme. Was zwar nur bedingt stimmt, aber das ist ein Thema für einen anderen Blog-Post.

Der Punkt mit der AI ist mir aber sehr wichtig, da ich von Anfang an transparent preisgeben möchte, wenn ich AI benutze und in welchem Umfang. So spare ich mir z.B. auch die Übersetzung von Texten, da ich ChatGPT einfach den deutschen Text übergebe und selbst dann nur den Output korrigiere.

Und ich halte es für notwendig, dass man sich selbst bewusst darüber wird, wie viel AI man verwendet, da es einerseits zu Problemen führen kann, wenn man sich zu sehr auf AI verlässt (Thema: 15 Risks and Dangers of Artificial Intelligence) und andererseits ein falsches Bild wird erzeugt, wenn wir nicht transparent zugeben, wann wir AI verwendet haben. Ich beziehe mich hier jetzt allerdings rein auf die Softwareentwicklung. Themen wie die Bildgenerierung mit AI, oder die Nutzung von AI für Texte, sind da noch einmal ein ganz anderes und vor allem viel komplexeres Thema, mit dem ich mich nicht wirklich öffentlich auseinandersetzen möchte, da ich nicht die Expertise habe, um eine differenzierte Meinung dazu zu haben.

Lediglich eines kann ich dazu sagen: Der penetrante Diebstahl durch AI von Kunstwerken und Texten ist mir unglaublich zuwider. Ich gebe zu, dass ich auch schon Bilder generiert habe, aber eher um zu sehen, was möglich ist. Der Trend, AI-generierte Bilder zu verkaufen, als Content zu nutzen oder gar als Kunst zu deklarieren, macht mich traurig und wirft ein schlechtes Licht auf das, wozu AI genutzt werden sollte, meiner Meinung nach. Nämlich als Tool, um die eigene Kreativität zu unterstützen und nicht um sie zu ersetzen. AI ist ein Werkzeug, kein Ersatz für das eigene Denken oder die eigene Kreativität.


Und schon befinden wir uns am Ende des Posts. Ich hoffe, ich konnte euch einen kleinen Einblick in die Tools und Frameworks geben, die ich für diese Webseite nutze. Bis zum nächsten Mal!

Liebe Grüße
Skorar

Ein Bild von Skorar