{}const=>[]async()letfn</>var
EntwicklungWeb

Wie man eine Frontend-Architektur erstellt, damit das Projekt nicht nach einem Jahr stirbt

Warum verwandeln sich einige Projekte nach sechs Monaten in Spaghetti-Code, während andere jahrelang leben und sich entwickeln? Wir analysieren die bewährten Prinzipien der Frontend-Architektur: von der Ordnerstruktur bis zu den Abstraktionsebenen. Erfahren Sie, wie Sie Code erstellen, der einfach zu skalieren, zu testen und zu warten ist.

К

Kodik

Autor

9 Min. Lesezeit

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“.

🔥 100.000+ Schüler sind bereits bei uns

Genug Theorie gelesen?
Zeit zu coden!

Kodik — eine App, in der du durch Praxis programmieren lernst. KI-Mentor, interaktive Lektionen, echte Projekte.

🤖 KI 24/7
🎓 Zertifikate
💰 Kostenlos
🚀 Jetzt starten
Heute beigetreten

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 Ressourcen

Prinzip: 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 Dienstprogramme

Jetzt 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! 🚀

🎯Hör auf zu zögern

Artikel gefallen?
Zeit zum Üben!

Bei Kodik liest du nicht nur — du schreibst sofort Code. Theorie + Praxis = echte Skills.

Sofortige Praxis
🧠KI erklärt Code
🏆Zertifikat

Keine Registrierung • Keine Karte