
MedAtlas
Bildungsanwendung
Ein Anatomieatlas für Studierende der Medizin und Gesundheitswissenschaften. Er lädt schnell auf dem Handy und funktioniert offline, mit Suche und Quiz direkt im Browser. Jeder Inhalt wird vor der Veröffentlichung automatisch geprüft.
Verwendete Technologien
Hauptmerkmale
- Vollständig statische Erzeugung — zur Laufzeit keine Datenbankabfrage
- Inhalte werden am Build-Tor mit Zod geprüft; ein ungültiger Datensatz stoppt die Bereitstellung
- Die Suche öffnet mit ⌘K und läuft im Browser; die Anfrage verlässt das Gerät nicht
- Beziehungen werden über Slugs statt über Identifikatoren gebildet
- Persönlicher Zustand (Lesezeichen, Fortschritt, Quiz) bleibt auf dem Client; Inhalte gelangen nie in den Client-Store
- Offline-Nutzung und Installierbarkeit über einen Service Worker
Entwicklungszeit
Problem
Anatomieinhalte bestehen aus zwei verschiedenen Materialien: auf der einen Seite rein strukturelle Daten — Ursprung, Ansatz, Nerv und Funktion eines Muskels — auf der anderen freie Erzählung, klinische Notizen, Differenzialdiagnose. Das Erste in einer Fließtextdatei zu halten, macht die Daten unsuchbar und inkonsistent; das Zweite in eine Datenbanktabelle zu pressen, macht das Schreiben unmöglich. Der eigentliche Punkt ist jedoch die Korrektheit: In Bildungsinhalten ist ein leer gelassenes Feld oder eine Referenz auf eine nicht existierende Struktur ein stiller Fehler für die lernende Person. Die Seite öffnet sich, die Tabelle erscheint, nur eine Zeile fehlt. Solche Fehler mussten vor der Veröffentlichung auffallen.
Nutzer
Ein Nutzertyp, aber zwei unterschiedliche Nutzungsmomente. Beim Lernen: lange Sitzungen, ständiges Springen von einer Struktur zur nächsten — vom Muskel zum Nerv, vom Nerv zur Region — und der Wunsch, die Suche über die Tastatur zu öffnen. Beim Wiederholen: vor der Prüfung, in der Pause, am Telefon, oft mit schlechter Verbindung. Der zweite Modus macht Offline-Betrieb und schnellen Start zwingend.
Meine Rolle
Einzelner Entwickler. Inhaltsschema und Prüfpipeline, Datenmodell, Struktur der statischen Erzeugung, Suche, Quiz-Ablauf, Offline-Schicht und Tests.
Architektur
Im Zentrum steht eine Entscheidung: keine Datenbank. Strukturdaten liegen als JSON im Repository, erzählende Inhalte als MDX; auf der Detailseite werden beide über einen Loader zusammengeführt. Beziehungen entstehen über Slugs statt über Identifikatoren — der Nerv eines Muskels heißt nicht "nerv-42", sondern trägt den lesbaren Namen dieses Nervs, was Adressen und Inhaltsdateien für Menschen bearbeitbar hält. Alle Detailseiten entstehen über `generateStaticParams` zur Build-Zeit; zur Laufzeit gibt es keinen Lesepfad zu einem Server. Der Standard ist Server Component; `"use client"` steht nur dort, wo es echte Interaktion gibt. Im Browser laufen drei Dinge: unscharfe Suche mit Fuse.js — der Index wird auf dem Client aufgebaut, das ⌘K-Modal am Desktop und die Vollbildebene am Telefon teilen denselben Körper — der in Zustand gehaltene persönliche Zustand und der Service-Worker-Cache. In Zustand liegen keine Inhalte, nur was der Person gehört: Lesezeichen, Fortschritt, Quiz-Zustand.
Zentrale Entscheidungen
Keine Datenbank — Inhalte im Repository, Prüfung zur Build-Zeit
Diese Inhalte ändern sich nicht hundertmal am Tag, sondern einige Male pro Semester und nur nach Durchsicht. Für solche Inhalte bringt eine Datenbank Laufzeitabfragen, Verbindungsverwaltung und eine eigene Verwaltungsoberfläche mit sich — und die Aktualität, die sie kauft, wird nicht gebraucht. Stattdessen liegen die Inhalte unter Versionskontrolle: Jede Änderung ist ein prüfbarer Diff und lässt sich zurücknehmen.
KompromissEine Inhaltsänderung erfordert Zugriff auf das Repository und eine neue Bereitstellung; eine Fachperson kann keine Korrektur allein vornehmen. Selbst ein Tippfehler wartet auf einen Build.
Ungültige Inhalte stoppen die Bereitstellung
Das Prüfskript liest alle Datendateien gegen Zod-Schemata und endet bei einem einzigen ungültigen Datensatz mit einem Fehlercode. Die TypeScript-Typen stammen aus denselben Schemata, das Schema ist also die einzige Quelle: Typ und Laufzeitprüfung können nicht auseinanderlaufen. Bei Bildungsinhalten ist dieses Tor der einzige Mechanismus, der "die Seite öffnet sich, aber die Information fehlt" vor der Veröffentlichung abfängt.
KompromissWeil das Schema streng ist, kann ein unfertiges Stück Inhalt nicht ins Repository; wer an einem Entwurf arbeiten will, muss entweder dem Schema ein leeres Feld erlauben oder den Inhalt außerhalb des Repositories halten. Das Schema zu lockern senkt jedoch unmittelbar den Wert des Tores.
Die Suche läuft im Browser, nicht auf dem Server
Der durchsuchte Datenbestand ist klein und fest; einen zur Build-Zeit ohnehin vorhandenen Index an den Client zu schicken, ist schneller und einfacher als eine Anfrage pro Tastendruck. Unscharfe Übereinstimmung war nötig, damit ein lateinischer Begriff und seine türkische Entsprechung mit derselben Anfrage gefunden werden. Desktop-Modal und mobile Ebene teilen sich Körper und Index.
KompromissDer Index wird auf den Client geladen; mit wachsendem Inhalt wächst dieser Aufwand, und irgendwann wird ein Wechsel zur serverseitigen Suche nötig. Außerdem werden keine Daten darüber erhoben, wonach gesucht wird — gut für den Datenschutz, ein blinder Fleck für die Priorisierung von Inhalten.
Persönlicher Zustand bleibt auf dem Client, Inhalte kommen nie hinein
Lesezeichen, Fortschritt und Quiz-Zustand gehören der Person, und es gibt keinen Grund, sie auf einem Server zu speichern — es gibt kein Konto und keine Synchronisation. Inhalte in den Client-Store zu kopieren, ist dagegen verboten: Inhalt ist die eine Quelle vom Server, und eine zweite Kopie läuft mit der Zeit auseinander.
KompromissWechselt jemand das Gerät, wandert der Fortschritt nicht mit; werden die Browserdaten gelöscht, geht er verloren. Das ist der Preis dafür, ohne Konto zu arbeiten, und er wurde akzeptiert.
Technologiewahl
Next.js App Router wird hier nicht als Server-Framework, sondern als statischer Seitengenerator genutzt: Hunderte Detailseiten entstehen über `generateStaticParams` zur Build-Zeit, und weil Server Component der Standard ist, bleibt das an den Client gesendete JavaScript minimal. Zod erfüllt zwei Aufgaben — Prüftor und Typquelle; dass beides aus einer Quelle kommt, garantiert, dass eine Schemaänderung auch den Typ ändert. MDX erlaubt es, klinische Erzählung als Fließtext zu schreiben, und lässt das Einbetten von Komponenten offen. Fuse.js ist klein genug für den Client. Serwist baut die Offline-Schicht auf, ohne einen Service Worker von Hand zu schreiben. Auf der visuellen Seite liefert interaktives SVG Zoom und Verschiebung, ohne in die Unschärfe eines Rasterbildes zu fallen.
Sicherheit
Die Anwendung hat keinen Schreibpfad: kein Formular, keine Sitzung, kein Konto, keine Nutzerdatenbank. Entsprechend werden auch keine personenbezogenen Daten gespeichert — Fortschritt und Lesezeichen verlassen das Gerät nicht. Die bei der Inhaltserstellung genutzten Modellaufrufe stehen nur in Skripten; zur Laufzeit geht keine Anfrage an einen externen Dienst. Das reduziert die Angriffsfläche auf die Korrektheit der Inhalte — und die schützt das Build-Tor.
Performance
Für dieses Projekt liegt keine gespeicherte, mit Datum versehene Lighthouse-Ausgabe vor, deshalb steht hier kein Seitengeschwindigkeits-Score. Strukturell gilt: Auf dem Lesepfad gibt es keine Serverabfrage, die Seiten werden als Build-Ergebnis ausgeliefert. Da Suchindex und Quiz-Zustand auf dem Client liegen, hängt die Interaktionslatenz nicht von einer Netzwerkrunde ab. Der Service Worker hält zuletzt gesehene Seiten im Cache; bricht die Verbindung ab, zeigt die Anwendung die eigene Offline-Seite statt eines leeren Bildschirms.
Ergebnisse
- 2026-04 → 2026-06 · 12 Commits
- 139
- 11 — Unit-Tests und Playwright-End-to-End-Abläufe
- 96 MDX-Dateien · 58 Muskeln · 25 Knochen · 22 Nerven · 14 Gelenke · 6 Regionen
- 18 Seitenvorlagen — Atlasmodule, Biomechanik, Quiz, Pinnwand und Offline-Seite
- Ein einziger Inhaltsdatensatz, der dem Schema nicht entspricht, stoppt die Bereitstellung
- Inhalte werden nie in den Client-Store kopiert; die einzige Quelle ist das Build-Ergebnis
Was ich heute anders machen würde
Das Prüftor habe ich von Anfang an eingebaut, und es war die Entscheidung, die sich in diesem Projekt am meisten ausgezahlt hat; dasselbe Muster habe ich später in Rahmina als Inhaltsfreigabe-Tor wiederholt — dort lautete die Regel nicht "passt es zum Schema", sondern "wurde es geprüft". Genau das fehlt hier: Das Schema prüft, dass ein Feld gefüllt ist, nicht, dass der Inhalt richtig ist. Heute würde ich jedem Inhaltsblock einen Prüfstatus geben und das Release-Tor auf "der Build scheitert, wenn ungeprüfter Inhalt existiert" umstellen. Der zweite Punkt ist die Suche: Den Index an den Client zu geben, ist beim heutigen Umfang richtig, aber nichts misst, wann diese Entscheidung kippt. Heute würde ich die Indexgröße mit einem Schwellwert begrenzen und den Build bei Überschreitung warnen lassen — wann eine Entscheidung ungültig wird, gehört genauso in den Code wie die Entscheidung selbst.
Finden wir gemeinsam den richtigen Einstieg
Sagen Sie mir, was Sie bauen möchten; ich sage Ihnen vorab, wie lange es dauert und was der sinnvolle erste Schritt ist.