
E-Commerce

Umfassende E-Commerce-Plattform für Bergaz Lebensmittel. Eine moderne Lösung, die Unternehmenspräsentation, Online-Verkauf und Kundenverwaltung unter einem Dach vereint.
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.
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.
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.
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.
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.
Alleiniger Entwickler. Datenmodell, Zahlungs- und Versandintegrationen, das Bestandsbewegungsjournal, das Verwaltungs- und das Verkaufspanel, die Hintergrund-Jobwarteschlange, die Sicherheitshärtung und das Vercel-Deployment.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Sagen Sie mir, was Sie bauen wollen; ich sage Ihnen vorab, wie lange es dauert und wo man anfängt.