Persönliche Portfolio-Website
Ein modernes, content-fokussiertes Portfolio, gebaut mit Next.js 16, React 19 und TypeScript. Von Grund auf mit KI-gestützter Entwicklung erstellt.
Jahrelang habe ich mir gesagt, ich würde eine persönliche Website bauen. Die Domain verstaubte. Die Idee verließ nie die Notizen-App.
Dann habe ich aufgehört zu planen und angefangen zu bauen. Zwei Tage später war diese Website live.
Was sich geändert hat
Früher dachte ich, eine Website zu veröffentlichen bedeutet wochenlange Arbeit. Schriften auswählen. Layouts debuggen. Texte schreiben, die sich nie richtig anfühlten. Diesmal hatte ich einen anderen Ansatz: mit KI bauen, schnell veröffentlichen, später verbessern.
Das Ergebnis: 51 Commits in 48 Stunden. Ein Blog mit Audio-Narration. Ein Kontaktformular, das tatsächlich funktioniert. Ein Design-System, das ich erweitern kann. Nicht perfekt, aber real.
Was mich überraschte, war nicht die Geschwindigkeit. Es war, wie sich die Arbeit anfühlte. Weniger Suchen, mehr Bauen. Weniger Zweifeln, mehr Veröffentlichen. Die Tools erledigten die mühsamen Teile, sodass ich mich auf das Wesentliche konzentrieren konnte.
Diese Seite ist die vollständige Geschichte. Wie alles zusammenkam, was unter der Haube steckt, und was ich beim nächsten Mal anders machen würde.

Tech Stack
| Kategorie | Technologie | Version | Zweck |
|---|---|---|---|
| Framework | Next.js (App Router) | 16.0.10 | Server-Komponenten, Routing, SSR |
| UI-Bibliothek | React | 19.2.3 | Komponenten-Architektur |
| Sprache | TypeScript | 5.x | Type Safety |
| Styling | Tailwind CSS | 4.x | Utility-first Styling |
| Komponenten | shadcn/ui | New York | Vorgefertigte zugängliche Komponenten |
| Content | next-mdx-remote | 5.0.0 | MDX-Rendering mit Frontmatter |
| Parsing | gray-matter | 4.0.3 | Frontmatter-Extraktion |
| Icons | Lucide React | 0.561.0 | SVG-Icon-Bibliothek |
| SendGrid | 8.1.6 | Kontaktformular-Zustellung | |
| Analytics | Google Analytics 4 | — | Traffic- und Verhaltens-Tracking |
| Audio | ElevenLabs Audio Native | — | Text-to-Speech für Blog-Posts |

Architektur
Die Anwendung folgt den Next.js App Router-Konventionen mit klarer Trennung zwischen Seiten, Komponenten, Content und Utilities.
ftchvs_26/
├── app/ # Next.js App Router Seiten
│ ├── layout.tsx # Root-Layout mit Schriften und Nav
│ ├── page.tsx # Startseite
│ ├── globals.css # Globale Styles und Design-Token
│ ├── about/ # Über-mich-Seite
│ ├── blog/ # Blog-Liste und [slug]-Seiten
│ ├── projects/ # Projekt-Liste und [slug]-Seiten
│ ├── contact/ # Kontaktformular mit API-Route
│ └── resume/ # Lebenslauf-Seite
├── components/ # React-Komponenten
│ ├── nav.tsx # Navigation mit mobilem Sheet
│ ├── theme-toggle.tsx # Dark/Light-Mode-Toggle
│ ├── tts-player.tsx # ElevenLabs Audio-Player
│ ├── mdx-content.tsx # MDX-Renderer
│ └── ui/ # shadcn/ui-Komponenten
├── content/ # MDX-Content-Dateien
│ ├── blog/ # Blog-Posts
│ ├── pages/ # Statische Seiten (about)
│ └── projects/ # Projekt-Fallstudien
├── lib/ # Utilities
│ ├── content.ts # Content-Laden und Parsen
│ ├── constants.ts # Website-Konfiguration
│ ├── schema.tsx # JSON-LD strukturierte Daten
│ └── utils.ts # Hilfsfunktionen
└── public/ # Statische Assets
Datenfluss
Content fließt von MDX-Dateien durch lib/content.ts zu den Seiten:
- MDX-Dateien speichern Content mit YAML-Frontmatter
- gray-matter parst Frontmatter in typisierte Objekte
- next-mdx-remote serialisiert MDX für React-Rendering
- Seiten-Komponenten laden Content zur Request-Zeit
- MDX-Komponenten rendern das finale HTML

Typografie-Design-System
Ich habe ein token-basiertes Typografie-System für konsistentes Styling auf allen Seiten gebaut. Jedes Token entspricht spezifischen Tailwind-Klassen.
| Token | CSS-Klasse | Styles | Verwendung |
|---|---|---|---|
| Seitentitel | .text-page-title | text-xl font-medium mb-6 | H1-Überschriften |
| Abschnittstitel | .text-section-title | text-lg font-medium mt-8 mb-4 | H2-Überschriften |
| Unterabschnitt | .text-subsection-title | text-base font-medium mt-6 mb-3 | H3-Überschriften |
| Fließtext | .text-body | text-[15px] leading-relaxed text-foreground/90 | Absätze |
| Fließtext-Container | .text-body-container | space-y-4 text-[15px] leading-relaxed | Mehrere Absätze |
| Meta | .text-meta | text-[14px] text-muted-foreground | Datum, Tags, Labels |
| Link | .text-link | underline underline-offset-2 hover:text-foreground | Inline-Links |
| Listenelement | .text-list-item | text-sm text-foreground/80 | Karten- und Listentext |
Schriftfamilie
- Geist Sans — Primäre Schrift für Fließtext und UI
- Geist Mono — Code-Blöcke und das Terminal-Logo
Beide Schriften werden über next/font/google mit display: swap für optimale Performance geladen.
Farbsystem
Farben verwenden den OKLCH-Farbraum für wahrnehmungsgleichmäßige Anpassungen. Das System umfasst:
- Light Mode — Neutrale Graustufen mit subtilen Akzenten
- Dark Mode — Dunkle Hintergründe mit hohem Kontrast-Text
- Semantische Farben — Primary, Secondary, Muted, Accent, Destructive, Success
Hauptfunktionen
Dark Mode
Theme-Präferenz wird in localStorage gespeichert und respektiert Systemeinstellungen beim ersten Besuch. Ein blockierendes Skript im <head> verhindert den Flash des falschen Themes.
<script dangerouslySetInnerHTML={{
__html: `(function(){
var t = localStorage.getItem('theme') ||
(matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light');
if (t === 'dark') document.documentElement.classList.add('dark')
})();`,
}} />
Text-to-Speech
Der Player lädt dynamisch, um Hydration-Probleme zu vermeiden:
const TTSPlayer = dynamic(
() => import('@/components/tts-player').then((mod) => mod.TTSPlayer),
{ ssr: false }
);
Einen Voice Clone auf ElevenLabs erstellen
Um deinen eigenen Voice Clone für den Audio-Player zu erstellen:
- Melde dich bei ElevenLabs an oder logge dich ein
- Navigiere zum Voices-Bereich im Dashboard (linke Seitenleiste)
- Klicke auf Add a new voice
- Wähle Professional Voice Clone (empfohlen für beste Qualität) oder Instant Voice Clone (schneller, erfordert weniger Audio)
- Lade Audio-Samples hoch:
- Professional Voice Clone: Mindestens 1 Stunde hochwertige Aufnahmen (ideal 2-3 Stunden für beste Ergebnisse)
- Instant Voice Clone: Minimum 30 Sekunden saubere Aufnahme
- Teile lange Aufnahmen in ~30-Minuten-Samples für einfacheres Hochladen auf
- Verwende saubere, hochwertige Aufnahmen ohne Hintergrundgeräusche, Echo oder unerwünschte Sounds
- Optional direkt im Interface aufnehmen mit Record yourself und bereitgestellten Skript-Vorlagen
- Benenne und labele deinen Voice Clone
- Bestätige, dass du das Recht und die Zustimmung hast, die Stimme zu klonen
- Klicke auf Save voice, um das Modell zu trainieren
- Nach dem Training kopiere die Voice ID aus den Stimmen-Einstellungen
- Konfiguriere sie im Projekt durch Aktualisierung von
ELEVENLABS_VOICE_IDinlib/constants.ts
Voice Sample
Hier ist ein Sample der geklonten Stimme:
Um das Audio-Sample zu generieren, kannst du den ElevenLabs Text to Speech Playground verwenden. Wähle deine geklonte Stimme, gib den Text ein, klicke auf Generate, dann lade herunter und speichere die Datei als voice-sample.mp3 im public/audio/-Verzeichnis.
Blog-Audio-Narration (lokales Kokoro)
Update — April 2026. Blog-Beiträge und die About-Seite nutzen jetzt Kokoro-82M, ein TTS-Modell unter Apache-2.0-Lizenz (82M Parameter, Stimme af_heart), das lokal auf der CPU per kokoro-onnx läuft. Die Generierung ist deterministisch, kostenlos und unabhängig von jeder externen API. Das Stimm-Sample oben bleibt die ursprünglich mit ElevenLabs geklonte Stimme — nur die Blog-Narration wurde migriert.
Das Setup ist einmalig: npm run setup:kokoro erstellt ein gitignoriertes .kokoro/-Verzeichnis mit einem Python-venv und dem ONNX-Modell (~340 MB). Danach narriert npm run generate-audio den gesamten englischen Inhalt und aktualisiert public/audio/manifest.json mit Post-Hashes. Auf einem Apple M4 Max läuft das Modell mit etwa 5× Echtzeit — ein drei Minuten langer Beitrag braucht ungefähr dreißig Sekunden.
Kontaktformular
Das Kontaktformular verwendet SendGrid für E-Mail-Zustellung mit:
- Client-seitiger Validierung
- Server-seitiger API-Route unter
/contact/api - Reply-to-Header für direkte Antworten
- Fehlerbehandlung mit benutzerfreundlichen Nachrichten
SEO
Jede Seite enthält:
- Metadata — Titel, Beschreibung, Keywords
- Open Graph — Social-Sharing-Bilder und Text
- Twitter Cards — Twitter-spezifische Formatierung
- JSON-LD — Strukturierte Daten für Person, Website, Article, Breadcrumb
- Sitemap — Automatisch generiert unter
/sitemap.xml - Robots — Suchmaschinen-Crawl-Regeln

Build-Prozess
Phase 1: Projekt-Setup
Gestartet mit create-next-app unter Verwendung des App Router-Templates. TypeScript, Tailwind CSS 4 hinzugefügt und shadcn/ui mit der New York-Style-Variante konfiguriert.
npx create-next-app@latest ftchvs_26 --typescript --tailwind --app
npx shadcn@latest init
Phase 2: Core-Layout
Das Root-Layout mit Geist-Schriften und einer responsiven Navigation gebaut. Die Nav enthält ein Desktop-Menü und ein mobiles Sheet (Slide-out-Drawer) von shadcn/ui.
Das Terminal-Logo (>_) verwendet eine Shimmer-Animation, die sich an Light- und Dark-Mode anpasst.
Phase 3: Content-System
lib/content.ts erstellt, um MDX-Laden zu handhaben:
getAllPosts()— Alle Blog-Posts sortiert nach Datum abrufengetPostBySlug()— Einzelnen Post mit serialisiertem MDX abrufengetAllProjects()— Alle Projekte abrufengetProjectBySlug()— Einzelnes Projekt mit serialisiertem MDX abrufengetPageBySlug()— Statische Seiten wie About abrufen
Jede Funktion liest aus dem Dateisystem, parst Frontmatter mit gray-matter und serialisiert Content mit next-mdx-remote.
Phase 4: Seiten
Sechs Haupt-Routen gebaut:
- Home — Einführung mit Liste aktueller Beiträge
- About — Beruflicher Hintergrund aus MDX geladen
- Blog — Post-Liste und einzelne Post-Seiten
- Projects — Projekt-Showcase (diese Seite)
- Resume — Berufserfahrung und Skills
- Contact — Formular mit SendGrid-Integration
Phase 5: Integrationen
SendGrid: API-Route konfiguriert, um E-Mails vom Kontaktformular zu senden. Der Absender muss in SendGrids Authentifizierungs-Einstellungen verifiziert sein.
Google Analytics 4: Über @next/third-parties hinzugefügt mit bedingtem Laden basierend auf NEXT_PUBLIC_GA_ID.
Phase 6: Design-System
Typografie-Design-Token in globals.css implementiert. OKLCH-Farbvariablen für Light- und Dark-Mode hinzugefügt. Shimmer-Animation mit CSS Custom Properties erstellt.
Phase 7: SEO
Metadata zu jeder Seite hinzugefügt. Sitemap- und robots.txt-Generatoren erstellt. JSON-LD strukturierte Daten für Person-, WebSite-, Article- und BreadcrumbList-Schemas implementiert.
Phase 8: Deployment
Zu GitHub gepusht und in Vercel importiert. Umgebungsvariablen konfiguriert:
NEXT_PUBLIC_SITE_URL— Kanonische URLNEXT_PUBLIC_GA_ID— Google AnalyticsSENDGRID_API_KEY— E-Mail-ZustellungCONTACT_EMAIL_TO— Empfänger-AdresseCONTACT_EMAIL_FROM— Verifizierter Absender

Changelog
Eine detaillierte Timeline der Entwicklung, extrahiert aus der Git-Commit-Historie. Neueste zuerst.
Entwicklungs-Statistiken
- Gesamte Commits: 51
- Entwicklungszeit: ~2 Tage
- Hauptfunktionen: 8
- Bugfixes: 12
- Refactors: 15
- Style-Updates: 16
13. Dezember 2025 — Tag 2: Polish & Features
Nacht: Finaler Polish
| Commit | Beschreibung |
|---|---|
20107f4 | Major: Umfassende SEO-Verbesserungen |
e3ec3c2 | About-Seiten-Intro vereinfacht |
c21eb7b | Voice-ID-Konfiguration zentralisiert |
9311bc6 | Custom ElevenLabs-Stimme konfiguriert |
f8533bf | Major: ElevenLabs optimiert, um API-Kosten zu reduzieren |
f8d31cf | TTS-Player über Summary positioniert |
7da002a | Blog-Listen-Typografie aktualisiert |
872a6ba | Blog-Post für Klarheit und Storytelling überarbeitet |
Abend: Audio-Optimierung
| Commit | Beschreibung |
|---|---|
fc9cea4 | Ref verwendet, um HTML einzufügen (Hydration-Fix) |
fb9303f | iframe-Embed für Audio Native verwendet |
7384478 | Suspense-Wrapper für MDX-Kompatibilität wiederhergestellt |
ff9ca71 | Major: Audio Native API mit vollständigen Content-Projekten |
76e6e08 | Suspense für SSR'd Audio Native entfernt |
36c512c | Audio Native via useEffect laden |
5073332 | Public User ID hardcoded |
ea4e5c7 | next/script für Audio Native-Laden verwendet |
8a5ee20 | Zu Audio Native-Integration vereinfacht |
32fa6ca | Skript zum Leeren des Audio-Cache hinzugefügt |
61382ca | Audio-Caching hinzugefügt, um API-Aufrufe zu reduzieren |
0ab2855 | TTS-Audio bei Seitenladen vorab generieren |
d59988f | Debug-Logging zu TTS-Player hinzugefügt |
Später Nachmittag: Audio-Integrations-Sprint
| Commit | Beschreibung |
|---|---|
d018e8e | ElevenLabs-Footer vom Player entfernt |
156eda5 | Geschwindigkeits-Auswahl minimalistischer gestaltet |
b87be4f | ElevenLabs-Header vom Player entfernt |
1d6af2f | Vollständige Audio-Player-UI sofort anzeigen |
1a90ebf | TTS-Player mit ElevenLabs-UI-Komponenten aufgewertet |
738ebb4 | Audio Native durch TTS API ersetzt |
fa6fd42 | Cursor IDE-Konfiguration und Regeln hinzugefügt |
5152aec | MDXRemote mit Suspense für React 19 gewrapped |
c7b54b2 | Major: ElevenLabs Audio Native TTS-Integration hinzugefügt |
Nachmittag: Typografie & Lebenslauf
| Commit | Beschreibung |
|---|---|
2ff75d4 | Lern-Statement im Blog-Post aktualisiert |
0c9b17b | Lebenslauf-Tagline aktualisiert |
3e6b185 | Strich-Präfix zu Skills und Ausbildungs-Items hinzugefügt |
3371a03 | "Looking For"-Sektion aus Lebenslauf entfernt |
649010d | Lebenslauf-Typografie und Ausrichtung verbessert |
f03743e | Schriftgrößen für Listenelemente, Links, Meta-Text aktualisiert |
Später Vormittag: Content-Strategie
| Commit | Beschreibung |
|---|---|
c5d35e4 | Zielgruppen-Targeting zur About-Seite hinzugefügt |
45e61a2 | "Tools selbst bauen"-Messaging abgeschwächt |
b7a8cd8 | README-Updates für neue Positionierung |
ae9f3a3 | Growth-Marketer-Positionierung auf allen Seiten verfeinert |
701a36f | Website als "Growth Marketer, der programmiert" neu positioniert |
db41419 | Projekte-Seite durch "Coming Soon"-Platzhalter ersetzt |
Vormittag: Design-System
| Commit | Beschreibung |
|---|---|
4b7f5ff | README mit Typografie-Token, SendGrid, GA4-Dokumentation aktualisiert |
d850b7e | Nav-Menü-Reihenfolge: About, Blog, Projects, Resume, Contact |
a17536b | Major: Typografie-Design-Token-System implementiert |
a91398f | Favicon auf Terminal-Prompt-Icon (>_) aktualisiert |
c85a8c8 | Focus-Ring-Border auf Terminal-Logo entfernt |
af50e11 | Shimmer-Animation für Light/Dark-Mode-Support refaktoriert |
12. Dezember 2025 — Tag 1: Foundation
| Commit | Beschreibung |
|---|---|
0b4e0a2 | Major: SendGrid für Kontaktformular mit erweiterter UI integriert |
5bf8d6a | Textauswahl auf Terminal-Logo-Klick gefixt |
2ab20b1 | Statische Generierung für MDX deaktiviert (React 19-Kompatibilitätsfix) |
0d1a813 | Seiten, Komponenten und Core-Features hinzugefügt |
891648c | README-Dokumentation gemerged |
08bc5e0 | Major: Initiale Next.js-Portfolio-Site mit Blog- und Projekt-Struktur |
7850991 | Repository-Initialisierung auf GitHub |
20aed18 | Initial Commit von Create Next App |
Wichtige Meilensteine:
- Vollständige App-Router-Struktur mit 6 Routen
- MDX-Content-System mit gray-matter-Parsing
- shadcn/ui-Komponenten-Bibliothek-Integration
- Kontaktformular mit E-Mail-Zustellung
- Responsive Navigation mit mobilem Drawer

Screenshots





Was ich gelernt habe
KI-gestützte Entwicklung ist real. Cursor und Claude erledigten Boilerplate, fanden Fehler und schlugen Patterns vor, die ich allein nicht gefunden hätte. Der Engpass verschob sich vom Tippen zum Denken.
TypeScript zahlt sich früh aus. Type-Fehler fingen Bugs, bevor sie den Browser erreichten. Die initiale Setup-Zeit sparte Stunden an Debugging.
Design-Token skalieren. Typografie-Klassen einmal zu definieren bedeutete konsistentes Styling ohne ständige Entscheidungen. Änderungen propagieren automatisch.
MDX ist flexibel. Frontmatter-Schemas lassen Content das UI steuern. Ein neues Feld hinzuzufügen dauert Minuten, nicht Refactors.
Nächste Schritte
- Mehr Projekte als Fallstudien hinzufügen
- Blog-Post-Suche und Filterung implementieren
- RSS-Feed für Blog-Abonnenten hinzufügen
- ISR (Incremental Static Regeneration) für Content-Updates erkunden
Outcomes
- •Von Grund auf mit KI-gestützter Entwicklung gebaut (Cursor + Claude)
- •Eigenes Typografie-Design-Token-System implementiert
- •ElevenLabs Audio Native für Text-to-Speech integriert
- •SendGrid für Kontaktformular-Funktionalität konfiguriert
- •Auf Vercel deployed mit umfassendem SEO