Zum Hauptinhalt
Zurück zu Projekten
Bergaz Lebensmittel logo

Bergaz Lebensmittel

E-Commerce

Kurz gesagt

Vorher
Der Verkauf lief über Telefon und Nachrichten; Bestand, Bestellungen und Zahlungseingänge wurden nicht an einem Ort verfolgt.
Nachher
Unternehmensvorstellung, Onlinebestellung sowie Bestands- und Bestellverwaltung liegen auf einer Website; das Vertriebsteam sieht nur die Verkaufsansichten und hat keinen Zugriff auf den Rest des Systems.
Was sich geändert hat
Auch wenn eine Zahlungsbestätigung doppelt eintrifft, wird die Bestellung nur einmal bestätigt, und zwei Kunden können nicht gleichzeitig dasselbe letzte Stück kaufen; das Live-Zahlungskonto schaltet der Betrieb selbst frei.
Bergaz Lebensmittel

Die E-Commerce-Website, die ich für Bergaz Lebensmittel entwickelt habe. Sie bündelt Unternehmensvorstellung, Onlineverkauf sowie Bestands- und Bestellverwaltung an einem Ort.

Eine Plattform, zwei Deployments

Dieser Shop ist keine einzeln geschriebene Website, sondern ein Deployment der von mir entwickelten E-Commerce-Plattform. Dieselbe Plattform läuft bei zwei Kunden — Bergaz Lebensmittel und Ezine Gurme. Gemeinsam ist der Kern: Datenmodell, Zahlungsablauf, Bestandsjournal, Rollensystem und sämtliche Panels. Kundenspezifisch sind Markenschicht, Inhalte und Integrationsschlüssel. Ohne diese Trennung von Anfang an hätte der zweite Kunde ein kopiertes Repository bedeutet — und in kopierten Repositories bleibt ein auf einer Seite behobener Fehler auf der anderen bestehen.

Vier Panels, eine Codebasis

  • Shop — Produktkatalog, Bildergalerie, Vergleich von drei Produkten nebeneinander, Warenkorb mit Gutschein- und Mengenrabatt, 3D-Secure-Checkout, Blog, Wunschliste und zuletzt angesehene Artikel.
  • Admin-Dashboard — Produkt- und Variantenverwaltung, Bild-Upload, Bestellverwaltung mit Sendungsverfolgung, Kunden-, Gutschein- und Bewertungsmoderation, Blog-CMS, Fragen und Antworten, Posteingang, Umsatzdiagramme und konfigurierbare Einstellungen.
  • Vertriebspanel — eine reduzierte Ansicht für Vertriebsmitarbeiter: gefilterte Bestellübersicht, Bearbeitung von Rückgabeanfragen und zentrale Leistungskennzahlen. Kein Zugriff auf den Rest des Systems.
  • Kundenkonto — Bestellhistorie mit Live-Status, mehrere Lieferadressen, Profil- und Passwortverwaltung, Rückgabeanfragen mit einem Klick und PDF-Rechnungen zum Download.

Rendering- und Cache-Strategie

Nicht jede Seite läuft mit derselben Strategie; die Entscheidung richtet sich nach dem Aktualitätsbedarf. Die Startseite wird über ISR alle 60 Sekunden erneuert und streamt über sechs Suspense-Grenzen. Der Katalog ist dynamisch: Filter und Paginierung stehen in der URL, sodass eine Filterkombination zu einer teilbaren Adresse wird. Unternehmenstexte (Über uns, Datenschutz, Rückgabe, Bedingungen) bleiben hinter einem täglichen ISR-Fenster statisch. Admin- und Vertriebspanel sind vollständig dynamisch — geschützt und in Echtzeit. Caches werden tag-basiert invalidiert: Eine Produktänderung erneuert die daran gebundenen Tags, nicht die gesamte Website. Warenkorb und Vergleichsliste liegen nicht auf dem Server, sondern in Client-Stores mit localStorage-Persistenz, so verliert ein nicht angemeldeter Besucher seinen Warenkorb nicht — Preis und Bestand werden beim Checkout jedoch serverseitig erneut geprüft; keine vom Client gelieferte Zahl gilt als vertrauenswürdig.

Zur Plattform selbst

Die Anwendung lässt sich aus dem Browser installieren, und da der zuletzt gesehene Katalog im Cache bleibt, führt eine abgebrochene Verbindung nicht zu einem leeren Shop. Bestell-, Versand- und Marketing-E-Mails laufen über eine Hintergrund-Warteschlange, sodass der Checkout nie an einem langsamen E-Mail-Anbieter hängt. Besucheranalysen bleiben in der Anwendung, statt an einen Dritten zu gehen.

Verwendete Technologien

  • Next.js 16
  • TypeScript
  • Prisma
  • PostgreSQL
  • NextAuth
  • iyzico

Hauptmerkmale

  • Vollständige E-Commerce-Infrastruktur
  • Sichere Zahlungsintegration
  • Bestands- und Auftragsverwaltung
  • SEO-Optimierung
  • Mobil-responsives Design

Entwicklungszeit

2,5 Monate

Problem

Der Verkauf lief über Telefon und Nachrichten; Bestand, Bestellungen und Zahlungen wurden nicht an einer Stelle geführt. Das Schwierige an einer E-Commerce-Plattform ist nicht die Storefront, sondern die Konsistenz zwischen Zahlung und Bestand: Sendet der Zahlungsanbieter denselben Callback erneut, greifen zwei Kunden gleichzeitig nach der letzten Einheit oder bricht eine Zahlung mittendrin ab, laufen Bestand und Bestellung auseinander. Dieses Auseinanderlaufen geschieht lautlos — niemand sieht einen Fehler, die Zahlen stimmen einfach nicht mehr.

Nutzer

Zwei Seiten. Der Käufer: Endkunde auf der Suche nach Käse mit geschützter geografischer Herkunft; wählt eine Gewichts- und Verpackungsvariante, will sehen, wie weit die Versandkostenschwelle noch entfernt ist, und sucht beim 3D-Secure-Schritt nach Sicherheit. Das Unternehmen: Die Rolle ADMIN verwaltet Produkte, Bestand, Bestellungen und Retouren; die Rolle SALES sieht ausschließlich die Verkaufsseite und erreicht den Rest des Systems nicht.

Meine Rolle

Alleiniger Entwickler. Datenmodell, Zahlungs- und Versandintegrationen, das Bestandsbewegungsjournal, das Verwaltungs- und das Verkaufspanel, die Hintergrund-Jobwarteschlange, die Sicherheitshärtung und das Vercel-Deployment.

Architektur

Next.js 16 App Router mit Neon PostgreSQL und Prisma. Der Schreibpfad ist Server Action → Zod-Validierung → Prisma-Transaktion. Der Zahlungsablauf hat fünf Schritte: Warenkorbprüfung (Bestand und Preis werden serverseitig erneut geprüft), Initialisierung von iyzico 3D Secure, Weiterleitung zur Bankverifizierung, Callback-Verarbeitung und Abschluss der Bestellung. Der Bestelldatensatz, die Bestandsminderung und der Eintrag der Bestandsbewegung werden in einer einzigen Transaktion geschrieben. Versand- und E-Mail-Aufgaben gehen in eine Hintergrund-Jobwarteschlange. Bilder liegen in Vercel Blob. Die Codeseite ist launch-ready; der Hauptzweig läuft auf Vercel als staging-ähnliche Produktion — da die produktiven iyzico- und Versand-Zugangsdaten ein operativer Cutover-Schritt sind, arbeitet der Zahlungsablauf derzeit gegen einen Mock-Anbieter.

Zentrale Entscheidungen

  1. Insert-first-Idempotenz beim Zahlungs-Callback

    Der Zahlungsanbieter kann denselben Callback erneut senden. Erst zu lesen, ob die Bestellung bereits bezahlt ist, und dann zu schreiben, ließ zwischen zwei gleichzeitigen Callbacks eine Race Condition offen. Das Callback-Token wird nun zuerst in eine Log-Tabelle mit Unique-Constraint geschrieben; der zweite Callback scheitert an diesem Schreibvorgang und wird nie verarbeitet.

    KompromissJeder Callback — auch die fehlgeschlagenen — schreibt eine Zeile; die Tabelle wächst fortlaufend und benötigt einen eigenen Aufräum-Job. Zudem muss ein eigener Codepfad mitgeführt werden, der eine Constraint-Verletzung nicht als Fehler, sondern als "bereits verarbeitet" liest.

  2. Bestandsminderung und Bestellanlage in derselben Transaktion

    Zwischen Bestandsprüfung und Bestandsminderung konnte eine andere Bestellung die letzte Einheit übernehmen. Lesen, Prüfen, Bestellanlage, Bestandsminderung und der Eintrag der Bestandsbewegung wurden in eine einzige Transaktion zusammengeführt; scheitert eine Zahlung, laufen Bestandswiederherstellung und Stornierung auf demselben Weg in einer Transaktion.

    KompromissDie Transaktion hält die betroffenen Variantenzeilen über den gesamten Zahlungsabschluss; gleichzeitige Bestellungen desselben Produkts reihen sich dahinter ein. In einem Lastmoment ist das eine unmittelbare Verzögerung für den wartenden Kunden.

  3. Geschlossene Allowlist für den Bild-Upload-Ordner

    Der Name des Upload-Ordners kam aus einer Administratoreingabe; offen gelassen waren Path Traversal und willkürliche Ordnerwucherung möglich. Die sechs zugelassenen Ordner sind als feste Menge im Code definiert, jeder Wert außerhalb löst einen Fehler aus.

    KompromissEinen neuen Ordner anzulegen erfordert nun eine Codeänderung und ein Deployment; über das Panel ist es nicht möglich. Flexibilität auf der Inhaltsseite wurde bewusst gegen eine kleinere Angriffsfläche eingetauscht.

  4. Turnstile an allen anonymen Schreib-Endpunkten, nicht nur beim Login

    Bot-Traffic bedeutet nicht nur Login-Versuche; Registrierung, Produktbewertungen, Kontaktformular, Newsletter-Anmeldung und Bestellabfrage sind ebenfalls Ziele. Die Verifizierung wird an all diesen Endpunkten serverseitig geprüft.

    KompromissAlle diese Formulare hängen nun von einem Drittanbieter-Skript ab; lädt es nicht, lässt sich das Formular nicht absenden. Jede Absendung verursacht zudem die Kosten einer zusätzlichen Verifizierungsanfrage.

Technologiewahl

Prisma hält Beziehungen und Migrationshistorie über ein Schema mit 31 Modellen nachvollziehbar, und die auf der Zahlungs- und Bestandsseite benötigte Unterstützung für mehrteilige Transaktionen ist bereits eingebaut. Neon PostgreSQL bleibt im selben Serverless-Modell wie das Deployment. Vercel Blob hält Bilder aus Repository und Datenbank heraus; die Bildoptimierung läuft über ein Remote Pattern. Der Rate Limiter liegt in der Datenbank statt im Speicher — unter Serverless machte es die Begrenzung sinnlos, dass jede Instanz ihren eigenen Zähler führt.

Sicherheit

Sechs HTTP-Security-Header sind definiert: CSP, nosniff, Framing-Verbot, Referrer-Policy, HSTS und eine eingeschränkte Permissions-Policy. Nutzereingaben (Bewertungen, Retourengründe, Bestellnotizen, Profiltexte, Kontaktformular) werden bereinigt. Das Rate Limiting läuft über einen zentralen, datenbankgestützten Limiter. Es gibt drei Rollen — ADMIN, SALES, USER — und jede geschützte Server Action prüft zusätzlich die Session. Die Mock-Zahlung wird über eine ausschließlich serverseitig lesbare Variable gesteuert und in der Produktion ohne ausdrückliche Freigabe abgelehnt. Retourenfreigaben und Bestandsbewegungen werden ins Audit-Log geschrieben.

Performance

Die automatische Memoization des React Compilers ist aktiv; Diagrammkomponenten werden per Dynamic Import aus dem Hauptbundle herausgelöst, und die Startseite streamt hinter Suspense-Grenzen. Der Server-Action-Cache wird tagbasiert invalidiert, sodass eine Produktaktualisierung den betreffenden Tag statt der ganzen Seite auffrischt. Bilder werden als AVIF und WebP ausgeliefert, die Bundle-Analyse liegt als fertiger Befehl im Repository. Zehn Playwright-Smoke-Tests decken Produktlebenszyklus, Gutschein- und Retourenabläufe ab. Lighthouse mobil (eigene Messung, Median aus 4 warmen Durchläufen): 59/100 — September 2026. Der Wert ist niedrig, und die Messung zeigt den Grund: Es kostet die Zeit bis zum ersten Byte, nicht blockierendes Skript — ein FCP-Median von 6,1 Sekunden bei einer Gesamtblockierzeit von 36 ms. Die Shopseite wird bei jeder Anfrage serverseitig erzeugt; der eigentliche Gewinn liegt darin, die Produktliste aus dem Rendering pro Anfrage in revalidierte statische Ausgabe zu überführen.

Ergebnisse

Schema
31 Prisma-Modelle · 32 Migrationen
TypeScript-Quelldateien
390
E2E-Testdateien
10 (Playwright)
Aktive Entwicklung
2026-05 → 2026-08 · 41 Commits
Zahlungsgarantie
Ein wiederholter Callback kann keine zweite Bestellbestätigung erzeugen; die Bestandsminderung teilt sich eine Transaktion mit dem Bestelldatensatz

Was ich heute anders machen würde

Die Zahlungs- und Bestandsseite würde ich heute genauso bauen, aber die Reihenfolge ändern: Das Idempotenz-Constraint würde ich zuerst schreiben. Hier habe ich mit einer "bereits bezahlt?"-Prüfung begonnen und die Race Condition erst danach geschlossen — genau das, was beim Problem der doppelten Bestätigung in Görev Kahramanı passiert ist, wo ich die Garantie ebenfalls nachträglich ergänzt habe. Zweitens würde ich die Bestands- und Zahlungsregeln früher in die Datenbank verlagern, um nicht den Wartungsaufwand zu wiederholen, den ich bei Bergaz Analiz dafür zahle, dass die Periodensperre in der Anwendungsschicht geblieben ist. Drittens bildet eine Performance-Messung bei gemockter Zahlung die reale Last nicht ab; vor dem Einsatz der produktiven Zugangsdaten und ohne eine mit Datum versehene Messung hat es keinen Sinn, einen Score zu veröffentlichen.

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.