iToverDose/Software· 26 JUNI 2026 · 20:07

Wie wir Bloom After von Vanilla JS zu Next.js + TypeScript migrierten

Die Migration eines bestehenden Frontends auf moderne Frameworks birgt Herausforderungen – doch sie ist essenziell für nachhaltige Codequalität. Ein studentisches Team zeigt, wie sie Bloom After von Vanilla JS zu Next.js mit TypeScript überführten und welche Lehren sie daraus zogen.

DEV Community4 min0 Kommentare

Die Entscheidung, ein funktionierendes Frontend zu migrieren, ist selten trivial. Doch genau solche Projekte prägen Entwickler:innen – besonders, wenn sie auf realen Codebasen stattfinden. Ein studentisches Team der Rise Academy stand vor dieser Herausforderung mit Bloom After, einer Anwendung, die sich durch Inkonsistenzen und veraltete Praktiken auszeichnete. Die Lösung? Ein moderner Tech-Stack mit Next.js und TypeScript.

Warum eine Migration trotz funktionierendem System sinnvoll war

Die ursprüngliche Version von Bloom After lief stabil, doch der Code entsprach nicht mehr den Anforderungen eines wachsenden Projekts. Die Aufgabenstellung der Rise Academy bestand darin, die Anwendung auf ein modernes Framework zu übertragen – nicht wegen technischer Defizite, sondern als praktische Übung. Diese Herangehensweise spiegelt den realen Arbeitsalltag wider: Migrationen sind selten Notfallmaßnahmen, sondern geplante Schritte zur Verbesserung der Codebasis.

Während der Arbeit traten jedoch konkrete Probleme zutage, die behoben werden mussten:

  • Uneinheitliche Datenstrukturen: Backend-Antworten nutzten teils _id, teils id sowie desc oder description – ein Albtraum für jede Codebasis.
  • Authentifizierung via `localStorage`: Eine fragwürdige Praxis, die Cross-Site-Scripting-Angriffe begünstigt.
  • Fehlende Code-Struktur: Ohne klare Vorgaben neigten Teammitglieder dazu, unterschiedliche Lösungen für ähnliche Probleme zu implementieren.

Das primäre Ziel war nicht die Rettung eines kaputten Systems, sondern die Anwendung bewährter Migrationsstrategien auf eine realistische Codebasis.

Strategische Vorarbeiten: Architekturentscheidungen, die Zeit sparten

Bevor das Team mit der eigentlichen Entwicklung begann, wurden grundlegende Entscheidungen getroffen, die spätere Konflikte verhinderten.

Konsistente CSS-Klassennamen beibehalten

Statt das gesamte Stylesheet neu zu schreiben, entschied sich das Team, alle bestehenden CSS-Klassennamen beizubehalten. Jede Seite importiert lediglich die benötigten Stile – ein globales Stylesheet wurde vermieden. Diese Strategie brachte mehrere Vorteile mit sich:

  • Keine doppelten Designarbeiten
  • Keine visuellen Regressionen durch falsche Klassennamen
  • Entwickler:innen und Designer:innen konnten auf vertraute Namen zurückgreifen

Route-Gruppen für geteilte Layouts nutzen

Next.js unterstützt Route-Gruppen, die zwar keinen Einfluss auf die URL haben, aber gemeinsame Layouts ermöglichen. Das Team strukturierte die Anwendung wie folgt:

app/
├── (public)/       # Marketing-Seiten – gemeinsamer öffentlicher Header
├── (admin)/        # Admin-Bereich – gemeinsamer Admin-Header
└── (dashboard)/    # Nutzer-Dashboard – gemeinsame Seitenleiste

Jede Gruppe verfügt über eine eigene layout.tsx-Datei, die automatisch das richtige Layout rendert – ohne bedingte Logik in den Komponenten.

Daten normalisieren – noch bevor die ersten Komponenten entstanden

Ein zentraler Schritt war die Definition von TypeScript-Schnittstellen, die die gewünschte Datenstruktur widerspiegelten – unabhängig von den tatsächlichen Backend-Antworten. Anschließend wurden Normalisierungsfunktionen erstellt, um die unordentlichen API-Antworten in saubere, vorhersehbare Strukturen zu überführen.

export interface Kurs {
  id: string;                      // Normalisiert aus _id oder id
  titel: string;
  beschreibung: string;            // Normalisiert aus desc oder description
  erstelltAm: string;
}

export function normalisiereKurs(rohdaten: Record<string, unknown>): Kurs {
  return {
    id: (rohdaten._id ?? rohdaten.id) as string,
    titel: rohdaten.titel as string,
    beschreibung: (rohdaten.description ?? rohdaten.desc) as string,
    erstelltAm: rohdaten.erstelltAm as string,
  };
}

Diese Normalisierungsschicht erwies sich als einer der größten Vorteile von TypeScript: Sobald sie implementiert war, konnten alle nachgelagerten Komponenten auf eine konsistente Datenstruktur vertrauen.

Woche 1: Fundament legen

Startseite und erste Komponenten

Um die gesamte Infrastruktur – Routing, CSS-Importe, Layouts – vorab zu validieren, begann das Team mit der Index-Seite. Erst nach deren Fertigstellung wurden die ersten gemeinsamen Komponenten entwickelt:

  • Teammitglieder: Zeigt das Team auf öffentlichen Seiten an
  • Vorschlagsfenster: Ein schiebbares Fenster für Nutzer:innen-Feedback

Diese Komponenten dienten als Referenz für die Projektstruktur, bevor weitere Teammitglieder Beiträge leisteten.

Zentrale API-Schicht für konsistente Anfragen

Statt jedem Entwickler die Freiheit zu geben, eigene fetch-Aufrufe zu schreiben, wurde eine zentrale API-Schicht erstellt:

// lib/api.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL;

async function anfrage<T>(
  methode: string,
  endpunkt: string,
  daten?: unknown
): Promise<T> {
  const antwort = await fetch(`${API_BASE_URL}${endpunkt}`, {
    method: methode,
    headers: { "Content-Type": "application/json" },
    body: daten ? JSON.stringify(daten) : undefined,
    credentials: "include",
  });

  if (!antwort.ok) {
    throw new Error(`${methode} ${endpunkt} → ${antwort.status}`);
  }

  return antwort.json();
}

export const api = {
  get: <T>(endpunkt: string) => anfrage<T>("GET", endpunkt),
  post: <T>(endpunkt: string, daten: unknown) => anfrage<T>("POST", endpunkt, daten),
  patch: <T>(endpunkt: string, daten: unknown) => anfrage<T>("PATCH", endpunkt, daten),
  del: <T>(endpunkt: string) => anfrage<T>("DELETE", endpunkt),
};

Für den lifestyle-Backend-Bereich wurde zusätzlich eine spezifische Wrapper-Funktion erstellt, die alle Anfragen an /lifestyle bündelt. Dies ermöglichte es Teammitgliedern, ohne Kenntnis der gesamten Backend-Struktur auf Daten zuzugreifen.

Woche 2: Admin-Bereich, Authentifizierung und Zugriffskontrolle

Migration von localStorage zu sicheren Cookies

Die ursprüngliche Anwendung speicherte Authentifizierungstokens in localStorage – eine Praxis, die anfällig für Cross-Site-Scripting-Angriffe ist. Die Lösung bestand in der Integration von NextAuth mit httpOnly-Cookies, die serverseitige Authentifizierungsprüfungen ermöglichen.

Die wichtigsten Schritte:

  • Benutzerdefinierte API-Routen für Login/Logout:
// app/api/auth/login/route.ts
import { cookies } from "next/headers";

export async function POST(anfrage: Request) {
  const { email, passwort } = await anfrage.json();
  const antwort = await fetch(`${process.env.API_URL}/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email, passwort }),
  });

  if (antwort.ok) {
    const { token } = await antwort.json();
    cookies().set("auth_token", token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "strict",
      maxAge: 60 * 60 * 24 * 7, // 1 Woche
    });
  }

  return antwort;
}

Diese Migration verbesserte nicht nur die Sicherheit, sondern ermöglichte auch serverseitige Authentifizierungsprüfungen – ein entscheidender Vorteil für zukünftige Erweiterungen.

Die Migration von Bloom After zeigt, dass selbst funktionierende Systeme von modernen Frameworks profitieren können. Durch strategische Vorarbeiten, konsistente Datenstrukturen und eine klare Architektur wurde der Übergang nicht nur reibungslos, sondern auch lehrreich. Die gesammelten Erfahrungen werden dem Team nicht nur in zukünftigen Projekten zugutekommen, sondern auch anderen Entwickler:innen als Best-Practice-Beispiel dienen. Die nächste Herausforderung? Die Anwendung um Echtzeit-Funktionen zu erweitern.

KI-Zusammenfassung

Learn how a student team migrated a legacy JavaScript frontend to Next.js with TypeScript, including authentication, route groups, and API centralization strategies.

Kommentare

00
KOMMENTAR SCHREIBEN
ID #4CNTD1

0 / 1200 ZEICHEN

Menschen-Check

9 + 8 = ?

Erscheint nach redaktioneller Prüfung

Moderation · Spam-Schutz aktiv

Noch keine Kommentare. Sei der erste.