Entstehungsprozess meiner neuen Website
Hier erfährst du, wie ich meine neue Website mit GoHugo & Pages CMS implementiert habe.

Ich wollte nun bereits seit längerer Zeit eine neue persönliche Website für mich entwickeln, da ich an der bisherigen Website einige Kritikpunkte hatte und inzwischen neue Technologien (u. a. GoHugo & Pages CMS) kenne und verwenden möchte. Die bisherige Website (website-v2) habe ich im Mai bis August 2022 entwickelt. An einem Samstag im März 2025 hatte ich, während dem Duschen, plötzlich einige gute Idee für eine neue Website und konnte es kaum abwarten, diese umzusetzen. Daher habe ich dann damit begonnen, meine Ideen zu sammeln und einen Prototyp in Penpot zu erstellen. Penpot ist eine Open-Source Alternative zu Adobe XD & Figma.

Die Erstellung des Prototyps hat total etwa 2.5-3h in Anspruch genommen. Diesen habe ich am besagten Samstagabend begonnen und anschliessend am Sonntagvormittag fertig gemacht.

Die Website soll wie die bisherige ein One-Pagger sein, das heisst alle Inhalte befinden sich auf einer Seite und sind in mehrere Abschnitte unterteilt. Dabei soll der oberste Abschnitt (sog. Hero-Section) meinen Namen und einige Stichworte zu mir enthalten. Zudem soll ein Knopf, welcher zum ersten Abschnitt scrollt, existieren.
Der erste "richtige" Abschnitt soll dann wie bisher eine "Über mich" Sektion sein. Anschliessend soll eine Sektion mit meinen Projekten folgen. Dabei sollen automatisch meine aktuellen Blogposts, YouTube Videos und GitHub Projekte geladen werden. Damit ich diese nicht manuell aufschalten muss, werde ich hierzu eine entsprechende API entwickeln, welche alle Projekte gebündelt zurückgibt. Darauf soll ein Abschnitt mit meinen Social Media Konten bzw. Kontaktoptionen folgen. Am Ende der Seite kommt dann noch ein Footer, mit Links zum Impressum, etc. sowie erneut den Social Media Links.
Um die ganze Website lebendig zu machen, werde ich überall Animationen einbauen. Mein Ziel ist es, hierfür lediglich CSS und, falls nötig, JavaScript zu verwenden. Ich möchte auf den Einsatz von externen Bibliotheken verzichten, um einerseits die Performance der Website zu verbessern und andererseits den Code einfach zu halten sowie meine Kenntnisse im Bereich der Webentwicklung zu verbessern.
Zur Umsetzung meiner Website habe ich mich für den Static Site Generator GoHugo entschieden. GoHugo ist schnell, Open-Source und bietet mir viele Möglichkeiten die Website strukturiert aufzubauen und Redundanzen zu vermeiden. Zudem möchte ich meine neue Website zweisprachig machen, einerseits auf Deutsch und andererseits in Englisch. Dies wollte ich bereits bei meiner bisherigen Website so machen, da ich diese jedoch ohne ein Framework wie GoHugo entwickelt habe, gab es keine optimale Lösung, weshalb ich diese dann ausschliesslich auf Englisch gemacht habe. Nun mit GoHugo ist die Implementation der Mehrsprachigkeit ein Leichtes.
Kurz vor dem Mittag habe ich mit der Umsetzung begonnen. Zuerst musste ich ein neues GitHub Repository anlegen und die Struktur für GoHugo erstellen.
Als Erstes habe ich mit der Entwicklung der Navigation begonnen. Ich wollte diese so clean wie möglich machen. Damit meine ich, dass keine Elemente redundant sind und die Anpassung an die Bildschirmgrösse komplett von CSS übernommen wird. Der Code soll ein Vorzeigebeispiel sein, wie man eine moderne Website programmieren kann.
<nav class=""> <!-- Class "small", "open" is possible -->
<div class="inner-width">
<a href="#" alt="Home"><img src='{{ "/assets/logo/logo_white.svg" | relURL }}' alt='{{ T "logo" }}' title='{{ T "logo" }}'></a>
<div class="nav-links">
<a href="#" alt='{{ T "home" }}'>{{ T "home" }}</a>
<a href="#about" alt='{{ T "about" }}'>{{ T "about" }}</a>
<a href="#projects" alt='{{ T "projects" }}'>{{ T "projects" }}</a>
<a href="#faq" alt='{{ T "faq-short" }}'>{{ T "faq-short" }}</a>
<a href="https://blog.michivonah.ch" alt='{{ T "blog" }}'>{{ T "blog" }}</a>
<a href="#contact" alt='{{ T "contact" }}'>{{ T "contact" }}</a>
</div>
<div class="nav-toggle">
<i class="ai-text-align-right"></i>
</div>
</div>
</nav>
HTML-Struktur der Navigation
Zum Wechseln zwischen den verschiedenen Zuständen der Navigation verwende ich CSS-Klassen, welche nach Bedarf hinzugefügt oder entfernt werden können. Nachdem ich mit der CSS-Umsetzung der Navigation fertig war, habe ich gleich die Funktionalität des Umschaltens mit JavaScript eingebaut. Der Übergang zwischen den Zuständen der Navigation ist gut gelungen. Ich könnte der Animation stundenlang zusehen. ;)

Anschliessend habe ich die Hero Section entwickelt. Im Hintergrund befindet sich eine animierte SVG-Grafik, welche ich mit BGJar generiert habe. Dem Titel sowie dem Scroll-Down-Button habe ich mit einer Animation etwas Leben eingehaucht.


Fertige Hero Section
Für die Icons auf meiner Website verwende ich Akar Icons. Damit ich die Icons direkt mit meiner Website ausliefern kann, habe ich diese heruntergeladen. Dadurch müssen diese nicht mehr von extern geladen werden, was die Performance verbessert. Bei der alten Website hatte ich teilweise Probleme damit, dass die Icons lange benötigten, bis sie geladen wurden. Im gleichen Zug habe ich auch die verwendete Schriftart von Google Fonts heruntergeladen, um diese ebenfalls direkt auszuliefern.
Als Nächstes habe ich mit der Projekte-Sektion begonnen. Da sich jedes meiner Projekte in eine von mehreren Kategorie einordnen lässt, wollte ich ein Filtersystem einbauen, ähnlich wie ich dies bei meiner Datenschutzerklärung gemacht habe. Daher konnte ich einiges vom Aufbau/Layout von dort übernehmen.

Ich habe gleich von Beginn an darauf geachtet, dass die Tags dynamisch aus Hugo kommen und nicht im HTML hardcoded sind. Hierzu habe ich folgenden tags.yml
angelegt:
- title: Blogpost
slug: blogpost
icon: ai-newspaper
checked: True
- title: YouTube
slug: youtube
icon: ai-youtube-fill
checked: True
- title: GitHub
slug: github
icon: ai-github-fill
checked: False
- title: Sonstiges
slug: sonstiges
icon: ai-paper
checked: True
tags.yml
Über diese Tags kann dann direkt mit GoHugo iteriert werden.
<div class="tag-filter fade-up">
{{ range .Site.Data.tags }}
<label class="tag" for="tag-{{ .slug }}" >
<input class="tag-checkbox" id="tag-{{ .slug }}" value="{{ .slug }}" type="checkbox" {{ if .checked }} checked {{ end }}>
<span class="tag-label">{{ .title }}</span>
</label>
{{ end }}
</div>
Anschliessend habe ich mit den Karten für die Projekte begonnen. Dabei habe ich das Layout aus meinem Prototyp nochmals überarbeitet und verschiedene Varianten ausprobiert. Mit den Karten wurde ich am Sonntag nicht mehr fertig, weshalb ich diese am folgenden Montagabend fertig gemacht habe. Dabei ist schlussendlich folgendes Design der Karten entstanden:

Momentan habe ich lediglich das Design der Projektsektion erstellt. Die Funktionalität werde ich im späteren Verlauf der Entwicklung implementieren.
Am Dienstagabend habe ich die Kontaktsektion erstellt.


Kontaktsektion
Hierbei habe ich eine Typewriter-Animation für den Titel sowie eine Hover-Animation für die Social Media Icons implementiert. Die Typewriter-Animation wird jedes Mal gestartet, wenn die Kontaktsektion sichtbar wird. Dies habe ich mit etwas JavaScript und einem Intersections-Observer erstellt. Dabei wird jeweils eine CSS-Klasse zum entsprechenden Text hinzugefügt, welcher die Animation auslöst.
function animationOnScroll(triggerSelector, animationClass, targetElement){
const trigger = document.querySelector(triggerSelector);
const target = ((targetElement) ? document.querySelector(targetElement) : trigger);
if(trigger){
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
target.classList.add(animationClass);
}
else{
target.classList.remove(animationClass);
}
});
}, { rootMargin: '0px 0px 5% 0px', threshold: 0.1 });
return observer.observe(trigger);
}
}
animationOnScroll('.contact-title-wrapper', 'typewriter-animation', '.contact-title');
Intersection Observer in JS (Quelle)
Am Mittwochabend habe ich den Footer implementiert. Diesen habe ich schlussendlich etwas anders als geplant gemacht. Er gefällt mir so jedoch auch und braucht nur wenig Platz.


Footer
Zudem habe ich dort die Übersetzung mittels GoHugo bereits vollständig eingebaut. Es ist kein Text hardcoded. Alles wird aus den Übersetzungsdateien geladen.
<div class="footer">
<div class="footer-part">
<p>© {{ dateFormat "2006" now }}</p>
<a href="{{ .Site.Params.legalLink }}" alt='{{ T "legal" }}' title='{{ T "legal" }}'>{{ T "legal" }}</a>
{{ with .Site.GetPage "copyright" }}
<a class="copyright-modal-link" href='#{{ .Params.slug }}' alt='{{ .Title }}' title='{{ .Title }}'>{{ .Title }}</a>
{{ end }}
<div class="sm-icons">
{{ range .Site.Data.socialmedia }}
{{ if .footer }}
<a href="{{ .link }}" alt="{{ .name }}" title="{{ .name }}"><i class="{{ .icon }}"></i></a>
{{ end }}
{{ end }}
</div>
</div>
<div class="footer-part">
<select class="language-selector" alt='{{ T "language" }}' title='{{ T "language" }}' name="language">
{{ range $.Site.Home.AllTranslations }}
<option value="{{ .Permalink }}" {{ if eq $.Site.Language.Lang .Lang }}selected{{ end }}>{{ .Language.LanguageName }}</option>
{{ end }}
</select>
</div>
</div>
Aufbau des Footers
Am Donnerstagabend habe ich lediglich eine Kleinigkeit integriert. Beim Scroll wird nun automatisch die Klasse small
zur Navigation hinzugefügt, was folgenden Effekt auslöst.

Am Freitagabend habe ich mich mit der "Über mich"-Sektion befasst und die Übersetzungen auf der ganzen Website weiterverbessert. Die "Über mich"-Sektion besteht aus zwei Teilen. Einerseits einem Bild von mir, mit einer simplen Hover-Animation. Daneben gibt es einen kurzen Text über mich. Wie bei der bisherigen Website, wird mein Alter auch hier wieder automatisch ausgerechnet, damit hier keine manuelle Anpassung nötig ist.


Abschnitt "Über mich"
Am Samstagabend habe ich mich um diverse Kleinigkeiten gekümmert und einiges an Optimierungen der Barrierefreiheit gemacht sowie Browserwarnungen behoben. Des Weiteren habe ich die Funktion zum Wechsel der Sprache eingebaut und einen Bug behoben, weshalb die Icons auf der englischen Version nicht angezeigt wurden.
Anschliessend habe ich die Entwicklung der API für die Projekte-Sektion begonnen und diese am folgenden Sonntagmorgen fertig gemacht. Die API ist mit JavaScript entwickelt und läuft bei Cloudflare als Worker. Dadurch kann ich die API kostenlos betreiben (bis 100'000 Anfragen pro Tag) und muss mich nicht um den Betrieb eines Servers kümmern. Zur Entwicklung der API habe ich auf ChatGPT zurückgegriffen, was erstaunlich gut funktioniert hat.
Die Blogposts werden direkt von der Ghost Content API abgerufen. Die YouTube Videos habe ich in eine eigene Playlist hinzugefügt, damit ich steuern kann, welche Videos auf der Website angezeigt werden. Die Informationen zu den YouTube Videos, wie URLs, Thumbnails und weiteres werden von der YouTube Data API abgerufen. Für die GitHub Projekte wollte ich zuerst ebenfalls die API von GitHub verwenden. Da diese mir jedoch keine Option bietet, auf die Listen zuzugreifen, welche ich erstellt haben, sondern nur auf alle meine Repositories, habe ich entschieden, hierfür eine JSON-Datei anzulegen, welche die Projekte enthält, welche auf meiner Website angezeigt werden sollen. Ebenfalls kann ich in dieser Datei andere Projekte erfassen, welche weder ein Blogpost, YouTube Video oder GitHub Repository sind. Somit habe ich die volle Flexibilität, über die angezeigten Projekte und neue Inhalte erscheinen automatisch ohne manuelle Anpassung. Die API unterstützt Paging und gibt die Projekte nach Datum sortiert zurück.
Anschliessend habe ich im JavaScript meiner Website die Funktion entwickelt, welche die Inhalte von der API lädt, die entsprechenden Karten erstellt und auf der Website einfügt. Beim Laden der Seite werden die aktuellsten sechs Projekte geladen. Am Montagabend habe ich die Funktion des "Load more"-Buttons implementiert, welcher jeweils drei weitere Projekte lädt.
Im Verlauf der nächsten Abenden habe ich die Animationen für die Tag-Filterung sowie diverse On-Scroll-Animationen gebaut und die Barrierefreiheit meiner Website verbessert. Die Animationen, welche beim Scroll auf der Website ausgelöst werden, wollte ich zuerst mittels dem Intersection Observer und JavaScript implementieren (ähnlich wie bei der Typewriter-Animation der Kontakt-Sektion). Dabei hatte ich dann Probleme, dass die Website teilweise beim Scroll geflackert hat. Am Donnerstagabend habe ich eine bessere Möglichkeit zur Umsetzung der On-Scroll-Animationen entdeckt, welche nur CSS braucht. Der einzige Nachteil dieser Lösung ist, dass diese nicht auf iOS bzw. allen WebKit-basierten Browsern läuft. Ich habe mich trotzdem dafür entschieden, da es meiner Meinung nach "the future way" ist. Da es sich nur um Animationen und keine essentielle Funktionalität meiner Website handelt, ist es verkraftbar, dass sich iOS-User noch etwas gedulden müssen, bis bei ihnen die Animationen funktionieren. Und wer weiss, vielleicht kommt dieses Feature ja bereits mit dem nächsten iOS-Update (Stand der Entwicklung).


On-Scroll-Animationen
Zudem habe ich am Freitagabend und Samstagnachmittag die fehlenden Funktionalitäten der Projekte-Sektion umgesetzt. Nun ist es möglich, die Projekte anhand der Kategorien zu filtern und es wird eine Meldung angezeigt, wenn keine Projekte in einer Kategorie verfügbar sind. Zusätzlich habe ich eine Ladeanimation eingeführt, welche beim Laden weiterer Projekte angezeigt wird.


Ladeanimation beim Laden von Projekten (gedrosselte Bandbreite)
Am Samstagabend habe ich mich dafür entschieden, wie auf der bisherigen Website, eine FAQ-Sektion einzubauen. Hierfür wollte ich den HTML-Tag <details>
verwenden, welcher es ermöglicht, ein Akkordeon-Menü (aufklappbarer Text) nur mit HTML, ohne jegliches JavaScript, zu implementieren. Die Fragen und die dazugehörigen Antworten habe ich dabei in einem eigenen Unterordner in GoHugo verwaltet. Dadurch ist es ein Leichtes, diese auch übersetzbar zu implementieren.
<div id="faq" class="faq">
<h1 class="fade-up">{{ T "faq" }}</h1>
<div class="faq-container">
{{ $faqs := where .Site.RegularPages "Section" "faq" }}
{{ range $faqs }}
<details id="faq-{{ .Params.slug }}" class="fade-up" name="faq">
<summary>{{ .Title }}</summary>
<div class="faq-answer">
{{ .Content | safeHTML }}
</div>
</details>
{{ end }}
</div>
</div>
Vollständige Implementation der FAQ-Sektion (HTML-only)

Des Weiteren habe ich einen Ladebildschirm implementiert, welcher beim Laden der Website angezeigt wird und per JavaScript ausgeblendet wird, sobald die Website geladen wurde. Hierbei habe ich mein Logo als SVG im HTML meiner Website eingefügt und per CSS animiert.


Ladebildschirm
Am Sonntag habe ich mich dann darum gekümmert, dass die API für das Laden der Projekte korrekt funktioniert, damit auch die Blogposts angezeigt werden. Dort gab es nämlich noch ein Problem, mit der HTTPS-Verschlüsselung in meinem Cloudflare Worker, welches dafür gesorgt hat, dass die Blogposts nicht geladen werden konnten. Ebenfalls habe für alle meine Projekte, welche noch kein Thumbnail hatten, ein Thumbnail erstellt. Zudem habe ich ein Bild erstellt, welches beim Teilen meiner Website auf Social Media angezeigt wird und die wichtigsten Infos enthält. Für beide Sprachen (Deutsch & Englisch) habe ich je ein eigenes Bild erstellt.


Karte beim Teilen über Social-Media
Am darauf folgenden Samstag habe ich die letzten, fehlenden Dinge implementiert und die Performance verbessert. Ich habe ein Popup eingebaut, um die Copyright-Hinweise anzuzeigen.

Um den LCP-Wert im Google PageSpeed-Test zu verbessern, habe ich den Ladebildschirm wieder entfernt. Er war zwar schön, ist aber nicht benötigt und verschlechtert lediglich die Performance meiner Website.

Ebenfalls habe ich die Filtermöglichkeit bei den Projekten entfernt, um die Bedienbarkeit zu vereinfachen und die Komplexität zu reduzieren. Bei meiner aktuellen Website musste ich nämlich feststellen, dass solche Gimmicks oft übersehen oder nicht richtig verstanden werden. Ggf. werde ich die Funktion künftig, mit Verbesserungen, wieder einführen.
Im Rahmen von allgemeinen Optimierungen und Bug-Fixes habe ich ein ASCII-Art als Easter-Egg implementiert.

Anschliessend habe ich die Security Headers für meine Website implementiert und diese unter meiner Domain live geschalten. Da ich Cloudflare Pages für das Hosting verwende, kann ich diese ganz leicht in der Datei _headers
definieren.

Am Sonntagmorgen, habe ich als Letztes, noch die Konfiguration für Pages CMS noch erstellt. Pages CMS ist ein Open-Source-CMS (Content-Management-System) für diverse statische Seitengeneratoren (wie GoHugo oder Astro). Damit lassen sich die Inhalte der Website über ein simples Web-Interface anpassen, anstelle dass die Markdown- und Konfigurationsdateien von GoHugo verändert werden müssen.

Einige Konfigurationen konnte ich, aufgrund der Mehrsprachigkeit meiner Website, nicht in Pages CMS abbilden. Die wichtigsten Inhalte lassen sich jedoch mittels Pages CMS bearbeiten.
Der Quellcode meiner neuen Website ist auf GitHub zu finden. Dort kannst du dir im Detail ansehen, wie ich die einzelnen Bestandteile meiner Website implementiert habe.
GitHub Repository meiner neuen Website
Meine neue Website ist ab sofort im Browser unter michivonah.ch zu erreichen.