Stellen Sie sich vor: Sie starten ein neues Projekt. Die Begeisterung ist auf dem Höhepunkt, der Code wird schnell geschrieben. Nach ein paar Monaten haben Sie bereits vergessen, warum Sie diese seltsame Funktion in utils.js benötigen. Nach sechs Monaten wird das Hinzufügen einer neuen Funktion zu einer echten Herausforderung. Nach einem Jahr verwandelt sich das Projekt in einen Sumpf, in dem jede Änderung alles kaputt machen kann.
Kommt Ihnen das bekannt vor? Dies ist das klassische Ergebnis einer fehlenden durchdachten Architektur. Lassen Sie uns herausfinden, wie man Frontend-Projekte erstellt, die jahrelang leben und sich entwickeln werden.

Warum scheitern Projekte?
Bevor wir über Lösungen sprechen, lassen Sie uns die Hauptursachen für das Scheitern von Projekten verstehen:
Spaghetti-Code. Alles hängt mit allem zusammen. Eine Änderung an einer Stelle beeinträchtigt drei andere Komponenten.
Fehlende Struktur. Die Dateien sind chaotisch verstreut. Die richtige Komponente zu finden, ist eine archäologische Expedition.
Logikduplizierung. Dieselbe Funktionalität wird an fünf verschiedenen Stellen auf fünf verschiedene Arten implementiert.
Keine Dokumentation. Selbst Sie selbst erinnern sich in einem Monat nicht daran, wie Ihr Code funktioniert.
Technische Schulden. „Später werde ich es korrigieren“ wird zu „Ich werde es nie korrigieren“.
Grundlagen: Ordner richtig strukturieren
Eine gute Architektur beginnt mit der Organisation von Dateien. Hier ist eine bewährte Struktur für die meisten Projekte:
src/
├── components/ # Wiederverwendbare Komponenten
│ ├── ui/ # Grundlegende UI-Elemente (Schaltflächen, Eingaben)
│ ├── layout/ # Layout-Komponenten (Header, Footer)
│ └── features/ # Geschäftskomponenten
├── pages/ # Anlagen
├── services/ # Arbeit mit API
├── store/ # Globaler Zustand (Vuex, Redux, Pinia)
├── utils/ # Hilfsfunktionen
├── hooks/ # Benutzerdefinierte Hooks (für React)
├── composables/ # Komponentenfunktionen (für Vue)
├── types/ # TypeScript-Typen
├── constants/ # Anwendungskonstanten
└── assets/ # Statische RessourcenPrinzip: Jeder Ordner ist für einen Verantwortungsbereich zuständig. Wenn Sie eine UI-Komponente benötigen, gehen Sie zu components/ui. Wenn Sie eine Funktion für die Arbeit mit der API benötigen, finden Sie diese in services.
Prinzip der alleinigen Verantwortung
Jedes Modul sollte eine Sache tun, aber es gut tun.
Schlecht:
// UserCard.js - macht alles auf einmal
function UserCard({ userId }) {
const [user, setUser] = useState(null);
// Datenabruf
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]);
// Validierung
const isValid = user?.email && user?.name;
// Formatierung
const formattedDate = new Date(user?.created).toLocaleDateString();
// Und noch ein Rendering...
return <div>...</div>;
}Gut:
// services/userService.js
export const fetchUser = async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
};
// utils/validation.js
export const validateUser = (user) => {
return user?.email && user?.name;
};
// utils/dateFormatter.js
export const formatDate = (date) => {
return new Date(date).toLocaleDateString();
};
// components/UserCard.js - nur Anzeige
function UserCard({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
if (!validateUser(user)) return null;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<span>{formatDate(user.created)}</span>
</div>
);
}Jetzt wird jede Funktion separat getestet, an verschiedenen Stellen verwendet und leicht modifiziert.
Abstraktionsebenen: Teilen und Herrschen
Gute Architektur wird in Schichten aufgebaut, wie eine Zwiebel:
1. Datenebene (Data Layer)
Verantwortlich für den Empfang und das Senden von Daten. Hier leben alle API-Anfragen.
// services/api/userApi.js
const API_BASE = 'https://api.example.com';
export const userApi = {
getUser: (id) => fetch(`${API_BASE}/users/${id}`).then(r => r.json()),
updateUser: (id, data) => fetch(`${API_BASE}/users/${id}`, {
method: 'PUT',
body: JSON.stringify(data)
}),
deleteUser: (id) => fetch(`${API_BASE}/users/${id}`, { method: 'DELETE' })
};2. Business-Logik-Schicht (Business Logic Layer)
Verarbeitet Daten, wendet Geschäftslogikregeln an.
// services/userService.js
import { userApi } from './api/userApi';
export const userService = {
async getActiveUsers() {
const users = await userApi.getUsers();
return users.filter(user => user.isActive);
},
async promoteToAdmin(userId) {
const user = await userApi.getUser(userId);
if (!user.email.endsWith('@company.com')) {
throw new Error('Only company emails can be admins');
}
return userApi.updateUser(userId, { role: 'admin' });
}
};3. Statusebene (State Layer)
Verwaltet den globalen Status der Anwendung.
// store/userStore.js (Pinia/Vue)
export const useUserStore = defineStore('user', {
state: () => ({
currentUser: null,
users: []
}),
actions: {
async loadUser(id) {
this.currentUser = await userService.getUser(id);
}
}
});4. Darstellungsschicht (Presentation Layer)
Komponenten, die dem Benutzer Daten anzeigen.
// components/UserProfile.vue
<script setup>
import { useUserStore } from '@/store/userStore';
const userStore = useUserStore();
const { currentUser } = storeToRefs(userStore);
onMounted(() => {
userStore.loadUser(route.params.id);
});
</script>
<template>
<div v-if="currentUser">
<h1>{{ currentUser.name }}</h1>
<p>{{ currentUser.email }}</p>
</div>
</template>Goldene Regel: Die oberen Schichten können die unteren verwenden, aber nicht umgekehrt. Komponenten verwenden Store, Store verwendet Services, aber Services importieren niemals Komponenten.

Komposition statt Vererbung
Im modernen Frontend gewinnt die Komposition über die Vererbung. Erstellen Sie kleine, wiederverwendbare Funktionen anstelle von riesigen Basisklassen.
Beispiel mit React-Hooks:
// hooks/useLocalStorage.js
export function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// hooks/useDebounce.js
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// Anwendung
function SearchComponent() {
const [query, setQuery] = useLocalStorage('searchQuery', '');
const debouncedQuery = useDebounce(query, 500);
// Jetzt haben Sie eine Suche mit Debounce und Speicherung in localStorage
useEffect(() => {
if (debouncedQuery) {
searchApi(debouncedQuery);
}
}, [debouncedQuery]);
}Typisierung ist Ihr bester Freund
TypeScript mag für Anfänger überflüssig erscheinen, aber es wird das Projekt vor vielen Problemen bewahren.
// types/user.ts
export interface User {
id: number;
name: string;
email: string;
role: 'user' | 'admin' | 'moderator';
createdAt: Date;
}
export interface ApiResponse<T> {
data: T;
error?: string;
status: number;
}
// services/userService.ts
export async function getUser(id: number): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}Jetzt schlägt die IDE verfügbare Felder vor, und TypeScript erkennt Fehler in der Entwicklungsphase und nicht in der Produktion.
Konfiguration und Konstanten
Verstreuen Sie keine magischen Zahlen und Zeichenfolgen im Code. Sammeln Sie sie an einem Ort.
// constants/config.js
export const API_CONFIG = {
BASE_URL: process.env.VITE_API_URL || 'https://api.example.com',
TIMEOUT: 5000,
RETRY_ATTEMPTS: 3
};
export const UI_CONSTANTS = {
ITEMS_PER_PAGE: 20,
DEBOUNCE_DELAY: 300,
TOAST_DURATION: 3000
};
export const ROUTES = {
HOME: '/',
PROFILE: '/profile',
SETTINGS: '/settings'
};Wenn Sie die Anzahl der Elemente auf der Seite ändern müssen, wissen Sie, wo Sie dies tun können.
Fehlerbehandlung
Ein systematischer Umgang mit Fehlern ist ein Zeichen einer ausgereiften Architektur.
// utils/errorHandler.js
export class ApiError extends Error {
constructor(message, status, data) {
super(message);
this.status = status;
this.data = data;
}
}
export async function handleApiCall(apiFunction) {
try {
return await apiFunction();
} catch (error) {
if (error instanceof ApiError) {
// Wir zeigen dem Benutzer eine verständliche Nachricht
toast.error(error.message);
// Protokollierung für Entwickler
console.error('API Error:', error.status, error.data);
} else {
// Unerwarteter Fehler
toast.error('Something went wrong. Please try again later.');
console.error('Unexpected error:', error);
}
throw error;
}
}
// Anwendung
async function loadUserData(userId) {
await handleApiCall(async () => {
const user = await userApi.getUser(userId);
if (!user) {
throw new ApiError('User not found', 404);
}
return user;
});
}Architekturentscheidungen dokumentieren
Erstellen Sie die Datei ARCHITECTURE.md im Projektstamm:
# Projektarchitektur
# # Struktur
- `/components` - переиспользуемые компоненты
- `/pages` - страницы приложения
- `/services` - бизнес-логика и API
# # Vereinbarungen
- Компоненты именуются в PascalCase
- Утилиты и сервисы в camelCase
- Константы в SCREAMING_SNAKE_CASE
# # Schichten
1. API Layer (services/api/)
2. Business Logic (services/)
3. State Management (store/)
4. UI Components (components/)
# # Wichtige Entscheidungen
- Используем Pinia для состояния
- Axios для HTTP запросов
- День.js для работы с датамиCode-Review und Linting
Richten Sie ESLint und Prettier sofort ein. Dies verhindert 90% der Probleme mit der Lesbarkeit des Codes.
// .eslintrc.js
module.exports = {
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'complexity': ['error', 10], // Warnt vor komplexen Funktionen
'max-lines-per-function': ['warn', 50]
}
};Architekturtests
Gute Architektur ist leicht zu testen.
// userService.test.js
import { userService } from './userService';
import { userApi } from './api/userApi';
jest.mock('./api/userApi');
test('promoteToAdmin rejects external emails', async () => {
userApi.getUser.mockResolvedValue({
id: 1,
email: 'external@gmail.com'
});
await expect(
userService.promoteToAdmin(1)
).rejects.toThrow('Only company emails');
});Wenn Ihre Funktionen schwer zu testen sind, ist dies ein Zeichen dafür, dass die Architektur schwach ist.
Skalierung: Feature-Based Structure
Wenn das Projekt wächst, gruppieren Sie den Code nach Funktionen und nicht nach Dateitypen:
src/
├── features/
│ ├── auth/
│ │ ├── components/
│ │ ├── services/
│ │ ├── store/
│ │ └── types/
│ ├── products/
│ │ ├── components/
│ │ ├── services/
│ │ └── store/
│ └── cart/
│ ├── components/
│ └── store/
└── shared/ # Allgemeine Komponenten und DienstprogrammeJetzt befindet sich alles, was mit der Autorisierung zu tun hat, in einem Ordner. Einfach zu finden, einfach zu löschen, einfach an einen anderen Entwickler zu übertragen.
Häufige Anfängerfehler!
Vorzeitige Optimierung
Es ist nicht nötig, sofort eine Architektur für eine Million Benutzer zu erstellen. Beginnen Sie mit einer einfachen, aber erweiterbaren Lösung.
Übermäßige Abstraktion
Wenn Sie nur eine Schaltfläche haben, müssen Sie dafür kein System aus fünf Basisklassen erstellen.
Konventionen ignorieren
Verwenden Sie die gängigen Praktiken Ihres Frameworks. Erfinden Sie das Rad nicht neu.
Kein Refactoring
Planen Sie Zeit für die Verbesserung der Architektur ein. Technische Schulden häufen sich unbemerkt an.
Praktische Tipps.
Beginnen Sie mit README. Beschreiben Sie, wie das Projekt funktioniert, bevor Sie Code schreiben. Das wird Sie dazu bringen, über Architektur nachzudenken.
Refaktorieren Sie regelmäßig. Nehmen Sie sich eine Stunde pro Woche Zeit, um den vorhandenen Code zu verbessern.
Lernen Sie von anderen. Studieren Sie Open-Source-Projekte, sehen Sie, wie sie organisiert sind.
Scheuen Sie sich nicht, etwas umzugestalten. Wenn die Struktur nicht funktioniert, ist es besser, sie jetzt zu korrigieren, als jahrelang mit ihr zu leben.
Hilfreiche Tools.
ESLint/Prettier — automatische Code-Formatierung
Husky — Überprüfung des Codes vor dem Commit
TypeScript – Standardisierung für Großprojekte
Storybook — Entwicklung von Komponenten in Isolation
Jest/Vitest — Logikprüfung
Befund
Eine gute Architektur ist nicht gleich beim ersten Mal ein perfekter Code. Es ist ein systematischer Ansatz, der es dem Projekt ermöglicht, sich weiterzuentwickeln. Beginnen Sie mit einer einfachen, aber logischen Struktur. Befolgen Sie das Prinzip der einzigen Verantwortung. Dokumentieren Sie wichtige Entscheidungen. Und vor allem überprüfen und verbessern Sie Ihren Code regelmäßig.
In einem Jahr werden Sie sich für jede Minute, die Sie in die Architektur investiert haben, danken. Ihr Projekt wird leben, sich entwickeln und Freude bereiten, und nicht zu einem Spaghetti-Ball werden, den man nicht anfassen kann.
Dies und vieles mehr kann man lernen in Kodike - alles im Detail analysieren und mit praktischen Aufgaben festigen. Wir bringen Ihnen nicht nur bei, wie man Code schreibt, sondern wie man die richtigen, skalierbaren Anwendungen erstellt, die viele Jahre halten werden.
Und wenn Sie Unterstützung benötigen, haben wir bereits mehr als 2000 Gleichgesinnte in einem aktiven Telegram-Kanal, wo Sie jede Frage stellen, architektonische Lösungen diskutieren und Ihren Code von erfahrenen Entwicklern überprüfen lassen können.
Schließe dich einer Community an, in der echte Profis wachsen! 🚀
