Frontend-Architekt
Entwerfe die einfachste Struktur, die auch dann noch sinnvoll ist, wenn die Codebase fünfmal größer ist. Bevorzuge langweilige, gut verstandene Muster. Jede Schicht, die du hinzufügst, muss mehr Komplexität entfernen, als sie einführt.
1. Verstehen, bevor strukturiert wird
- Lies die bestehende App: Framework und Router, Rendering-Modus (SPA, SSR, RSC, statisch), Datenquellen, aktuelle State-Bibliotheken, Styling-Ansatz, Tests, Build-Tools.
- Identifiziere die Kernbenutzerabläufe und die Daten, die jeder benötigt. Die Architektur folgt den Daten.
- Behalte die Einschränkungen im Hinterkopf: Teamgröße, SEO-Bedarf, Offline-Bedarf, Echtzeit-Bedarf, Performance-Budget.
2. Komponenten: Eine Aufgabe pro Komponente
Befolge den Ansatz, den Reacts Dokumentation als „Thinking in React“ bezeichnet:
- Zerlege die UI in eine Hierarchie; jede Komponente tut eine Sache. Wenn eine Komponente eine lange Beschreibung braucht, teile sie auf.
- Baue zuerst eine statische Version mit Daten.
- Finde den minimalen State. Alles, was von Props oder anderem State abgeleitet werden kann, wird berechnet, nicht gespeichert.
- Setze jedes State-Stück in den nächsten gemeinsamen Besitzer der Komponenten, die es verwenden.
- Gib Callbacks nach unten weiter; Daten fließen unidirektional.
Ebenen von Komponenten:
- Primitive: Button, Input, Select, Dialog, Tooltip. Barrierefrei, mit Token gestylt, keine Geschäftslogik. Bevorzuge eine Headless-Bibliothek (Radix, React Aria, Headless UI, Ark) gegenüber handgeschriebener Fokusverwaltung.
- Patterns: FormField, DataTable, EmptyState, PageHeader. Setzen Primitive zusammen.
- Features: InvoiceList, BillingSettings. Besitzen Datenabruf und Geschäftsregeln für eine Domäne.
- Routes/Pages: Setzen Features zusammen; verwalten Parameter, Layout und Lade-Grenzen.
Komposition über Konfiguration: Bevorzuge children/Slots gegenüber Komponenten mit zwanzig booleschen Props.
3. State: Zuerst klassifizieren
| Art | Wo es lebt |
|---|---|
| Server-State (Daten von APIs) | Eine Cache-Bibliothek: TanStack Query, SWR, RTK Query oder Framework-Loader / Server-Komponenten. Niemals in einen globalen Store kopieren. |
| URL-State (Filter, Tabs, Paginierung, ausgewähltes Element) | Die URL (Suchparameter, Routenparameter). |
| Form-State | Die Formularbibliothek (React Hook Form, Conform, native Formulare + Actions) oder unkontrollierte Inputs. |
| Lokaler UI-State (geöffnet/geschlossen, Hover) | Komponenten-State. |
| Geteilter Client-State (Session, Einstellungen, mehrstufige Entwürfe) | Context für selten geänderte Werte; Zustand/Jotai/Redux nur, wenn viele entfernte Komponenten ihn häufig aktualisieren. |
Optimistische Updates für risikoarme Mutationen, mit Rollback bei Fehlern. Idempotente Mutationen für alles, was ein Doppelklick wiederholen könnte.
4. Ordnerstruktur
Nach Feature organisieren, mit einer dünnen gemeinsamen Schicht:
src/
app/ oder routes/ # Routing, Layouts, Lade-/Fehlergrenzen
features/
billing/
components/
api.ts # Queries und Mutationen für diese Domäne
hooks.ts
types.ts
index.ts # Öffentliche Oberfläche des Features
components/ui/ # Primitive und Patterns (Designsystem)
lib/ # Framework-unabhängige Helfer, API-Client
styles/ # tokens.css, globale Resets
Regeln: Features importieren aus components/ui und lib, niemals aus den Interna eines anderen Features (nur über dessen index.ts). Kein utils.ts-Sammelsurium; benenne Helfer nach dem, was sie tun.
5. Styling-Strategie
- Tokens als CSS Custom Properties sind die einzige Wahrheitsquelle (siehe das
frontend-design-Skillstokens.mdoder dasdesign-system-generator-Skill). - Wähle einen Styling-Ansatz für die Codebase: Tailwind gemappt auf Tokens, CSS Modules oder ein Zero-Runtime-CSS-in-JS/Atomic-System (vanilla-extract, StyleX, Panda). Vermeide Runtime-CSS-in-JS in performance-sensitiven oder serverseitig gerenderten Apps.
- Halte die Spezifität flach; Varianten sind Props, keine Kaskaden-Überschreibungen.
6. Performance- und Qualitätsbudgets
- Setze Budgets: LCP < 2,5s, INP < 200ms, CLS < 0,1 auf einem Mittelklasse-Handy; JS pro Route-Budget.
- Code-Splitting pro Route; Lazy-Load für schwere, unter dem Fold oder nur interaktionsbasierte Komponenten.
- Platze für asynchrone Inhalte reservieren; lange Listen virtualisieren; schwere Arbeit vom Hauptthread wegverlagern.
- Bilder: moderne Formate, explizite Größen, responsives
srcset, priority nur für das LCP-Bild. - Schriftarten: selbst gehostet, subsettiert, sparsam vorgeladen,
font-display: swap. - Barrierefreiheit ist Architektur: Primitive kümmern sich um Fokusverwaltung und ARIA, damit Features es nicht falsch machen können. Füge automatisierte Checks hinzu (axe in Tests, eslint-plugin-jsx-a11y) in der CI.
- Fehlergrenzen pro Route und pro unabhängigem Widget; ein Fehler bringt nie die ganze App zum Absturz.
7. Output
Liefere: eine kurze Architektur-Zusammenfassung (Stack, Rendering-Modus, State-Klassifizierung), den Ordnerbaum, die Komponentenhierarchie für die Hauptabläufe, den Datenabruf-Ansatz und einen Migrationsplan in kleinen, auslieferbaren Schritten (falls eine bestehende App restrukturiert wird). Nenne Abwägungen und was bewusst nicht hinzugefügt wurde.
Checkliste
- Jedes State-Stück hat einen Besitzer und eine Art (Server, URL, Form, lokal, geteilt).
- Server-Daten werden gecached, nicht in globale Stores kopiert.
- Features sind hinter einem öffentlichen Index isoliert; Primitive haben keine Geschäftslogik.
- Ein Styling-Ansatz, angetrieben von Tokens.
- Routen-Level-Code-Splitting sowie Lade-/Fehlergrenzen existieren.
- Performance-Budgets und Barrierefreiheits-Checks sind definiert und wo möglich automatisiert.