Start
Alle Projekte

Rank tracker

Entwicklung der Architektur und des MVP eines SaaS-Dienstes von Grund auf

Rolle

Fullstack Developer / Software Architect (Solo)

Jahr

2026

Stack

NestJS 11Next.js 16 (React 19)Prisma 7PostgreSQL 16Redis 8BullMQTurborepoDockerDokploy

Die Aufgabe

Die Aufgabe

Produktziel:
Entwicklung einer vollwertigen SaaS-Plattform von Grund auf für SEO-Spezialisten und Website-Betreiber zur Automatisierung des Suchmaschinen-Rankings in Yandex und Google. Der Dienst soll manuelle Überprüfungen überflüssig machen: Nutzer laden lediglich eine Liste von Keywords hoch (unter Berücksichtigung von Region und Endgerät), woraufhin das System zeitgesteuert Daten sammelt und die Ranking-Dynamik (Wachstum, Abstürze, Top-3-Platzierungen) in einer interaktiven Pivot-Tabelle anschaulich darstellt.

Ingenieurtechnische Herausforderung:
Im Rahmen dieses Pet-Projekts bestand die Aufgabe darin, nicht nur ein einfaches MVP, sondern eine produktionsreife (production-ready) Architektur zu entwerfen, die für reale Lasten und Skalierung ausgelegt ist. Folgende Anforderungen mussten erfüllt werden:

  1. Aufbau einer fehlertoleranten, asynchronen Pipeline zur Datenerfassung (SERP), die Restriktionen und Eigenheiten (Quirks) externer APIs umgehen kann.
  2. Vermeidung von Race Conditions bei der Berechnung von Nutzerkontingenten (FREE/PAID-Tarife) zwischen dem Planer (Scheduler) und manuellen Starts.
  3. Gewährleistung einer durchgehenden Typsicherheit (End-to-End Type Safety) zwischen den unabhängigen Frontend- und Backend-Anwendungen ohne Code-Duplizierung.
  4. Implementierung eines strengen, mehrstufigen Rate-Limitings, um sowohl den eigenen Server vor Missbrauch als auch die kostenpflichtige externe API vor Überschreitungen zu schützen.

Die Phasen

01

Konzeption & Architektur (Spec-first)

  • Die Entwicklung begann mit dem Verfassen von Design-Dokumenten (Scope, Goals, Non-Goals) für jede Systemkomponente.
  • Um eine Single Source of Truth zu etablieren, wurde ein Monorepo auf Basis von Turborepo eingerichtet.
  • Validierungsschemata wurden in Zod geschrieben und in ein gemeinsames @rank-tracker/shared-Paket ausgelagert, was eine 100%ige Synchronisation der API-Verträge zwischen Backend (NestJS) und Frontend (Next.js) garantierte.
02

Sicherheit, Auth & Datenmodellierung

  • Einrichtung der PostgreSQL-Datenbank mit Prisma ORM (das Schema durchlief 11 Migrationen).
  • Implementierung einer zustandslosen (stateless) Authentifizierung: JWT-Token werden ausschließlich in httpOnly-Cookies gespeichert.
  • Für eine nahtlose User Experience (UX) wurde in Next.js ein BFF-Proxy eingerichtet, der 401-Fehler abfängt und ein „stilles“ (silent) Token-Refresh ohne Neuladen der Seite durchführt.
03

Kernsystem (Asynchrone SERP-Pipeline)

  • Entwicklung der Positionstracking-Engine mit BullMQ-Workern.
  • Um Race Conditions beim Abzug von Nutzerkontingenten zwischen dem nächtlichen Cronjob und manuellen Abfragen zu verhindern, wurden atomare Lua-Skripte für Redis geschrieben.
  • Zudem wurde ein XML-Parser für Suchmaschinen-Antworten integriert, der über einen Exponential-Backoff-Mechanismus verfügt, um spezifische API-Fehler (z.B. "Code 110") zu umgehen.
04

Frontend & UX

  • Entwicklung eines responsiven Dashboards basierend auf React 19, Tailwind CSS 4 und shadcn UI.
  • Das Herzstück ist eine komplexe, interaktive Pivot-Tabelle, die die Historie der Platzierungen visualisiert.
  • Hinzugefügt wurde ein ausfallsicheres Import-Modul für CSV/XLSX-Dateien, das fehlerhafte Zeichenkodierungen (win1251) automatisch erkennt und korrigiert.
05

Infrastruktur & CI/CD

  • Die Anwendungen wurden in leichtgewichtige Docker-Container verpackt (Multi-Stage-Builds).
  • Es wurde eine automatisierte CI/CD-Pipeline in GitHub Actions konfiguriert: Ausführung von Lintern, Unit-Tests des Parsers mit echten XML-Fixtures, Erstellung von Images, Push in die GHCR und automatisches Deployment auf dem Produktionsserver via Dokploy.

Der Prozess

Rank Tracker — SaaS-Tool zur Positionsverfolgung bei Yandex und Google

Pet-Projekt: ein vollwertiger SaaS-Dienst zur Überwachung von Website-Rankings in Suchmaschinen — von der Idee und den Spezifikationen bis zu CI/CD und Produktions-Deployment.

Stack in einer Zeile: TypeScript · NestJS 11 · Prisma 7 · PostgreSQL 16 · Redis 8 · BullMQ · Next.js 16 (App Router, React 19) · Tailwind CSS 4 · shadcn/Base UI · TanStack Query 5 · Zod 4 · Turborepo · Docker · GitHub Actions → GHCR · Dokploy


Idee

SEO-Spezialisten und Website-Betreiber müssen wissen, auf welchen Positionen ihre Website bei wichtigen Suchanfragen erscheint — und wie sich diese Positionen im Zeitverlauf verändern. Rank Tracker löst genau das:

  • der Nutzer legt ein Projekt (eine Website-Domain) an und fügt Keywords hinzu — mit Angabe der Suchmaschine (Yandex / Google), der Region und des Gerätetyps (Desktop / Mobile / Tablet);
  • das System fragt nach Zeitplan (oder auf manuellen Anstoß) die Suchergebnisse über eine SERP-API (xmlstock.com) ab, ermittelt die Domain-Position und speichert sie in der Historie;
  • das Dashboard zeigt eine Pivot-Tabelle der Positionshistorie (Datumsspalten, neueste links), in der eine Top-3-Platzierung gold, ein Anstieg grün und ein Abfall rot hervorgehoben wird, ergänzt um Kennzahlen-Karten: Gesamtzahl der Keywords, Durchschnittsposition, Anzahl in den Top 3.

Das Projekt ist als SaaS konzipiert: FREE-/PAID-Tarife, tägliche Check-Kontingente (FREE — 20/Tag), Missbrauchsschutz, Admin-Tools zur Tarifverwaltung.

Funktionen

  • Authentifizierung: Registrierung mit E-Mail-Bestätigung, JWT-Login (15 Min. Access + 30 Tage Refresh mit Rotation) in httpOnly-Cookies; Cloudflare Turnstile und ein Registrierungslimit pro IP.
  • Projekte: CRUD, Domain pro Nutzer eindeutig, konfigurierbares Intervall für automatische Checks.
  • Keywords: Massenimport per Texteinfügung oder CSV/XLSX-Import (bis 2 MB, automatische Erkennung defekter win1251-Kodierung); kaskadierende Auswahl: Suchmaschine → Region (nach Engine gefiltert) → Gerät; Deduplizierung auf DB-Ebene.
  • Positions-Checks: täglicher Cronjob + manueller Start (alle Keywords oder eine Auswahl); Yandex — Top 100, Google — Top 50; tägliche Kontingente pro Nutzer.
  • Dashboard: Pivot-Tabelle der 30-Tage-Positionshistorie mit farblich codierten Trends, Kennzahlen-Karten, Umschalter Yandex/Google (Zustand in der URL).

Architektur

Ein pnpm + Turborepo-Monorepo mit drei Workspaces: apps/api (NestJS), apps/web (Next.js) und packages/shared — ein Paket mit Zod-Schemas, das die API-Verträge zwischen Frontend und Backend konsistent hält.

Pipeline der Positions-Checks

Der Kern des Projekts ist eine asynchrone Rank-Collection-Pipeline auf Basis von BullMQ:

Schritt für Schritt:

  1. Trigger — täglicher Cronjob oder manueller Start aus der UI.
  2. Auswahl — der Scheduler reiht nur Keywords ein, deren letzter Check älter ist als das checkInterval des Projekts.
  3. Kontingent — ein atomares Redis-Lua-Skript reserviert Slots innerhalb des Tageslimits des Nutzers. Zwei Modi: reserveExact (alles-oder-nichts für manuelle Checks, sonst 429 mit used/limit-Details) und reserveUpTo (best-effort für den nächtlichen Cronjob — nimmt, was übrig ist).
  4. Queue — Jobs erhalten 3 Versuche mit exponentiellem Backoff; wiederholt wird nur, was als retryable markiert ist.
  5. Fetch — ein gemeinsamer Token-Bucket begrenzt Aufrufe an die kostenpflichtige API auf 20 Req/s; Yandex liefert dank Deep Grouping Top-100 in einem Request, Google begrenzt auf 10 Ergebnisse pro Seite — der Service durchläuft bis zu 5 Seiten mit frühzeitigem Abbruch und behandelt die xmlstock-Eigenheit: Code 110 ("Anfrage in Warteschlange") → dieselbe Anfrage bis zu 4-mal mit 7 s Pause wiederholen.
  6. Parsing und Speichern — defensives XML-Parsing ohne any, Domain-Normalisierung (Protokoll/www/Port/Pfad), Speichern eines PositionCheck-Datensatzes (null = Domain nicht in den Top-Ergebnissen).

Datenmodell

Technologie-Stack

SchichtTechnologien
BackendNestJS 11, Prisma 7 (@prisma/adapter-pg, Multi-File-Schema), PostgreSQL 16, Redis 8 + ioredis, BullMQ, passport-jwt, argon2, nodemailer, fast-xml-parser, Joi (Env) + Zod/nestjs-zod (DTO)
FrontendNext.js 16 (App Router, RSC, standalone), React 19, TypeScript, Tailwind CSS 4, shadcn auf Base UI, TanStack Query 5, Cloudflare Turnstile
Shared@rank-tracker/shared — Zod-4-Schemas und API-Verträge (single source of truth für beide Anwendungen)
Importcsv-parse, SheetJS (xlsx), iconv-lite
InfraTurborepo, Docker Multi-Stage (turbo prune --docker, node:22-alpine, non-root), docker-compose (Postgres + Redis mit ACL), GitHub Actions → GHCR, Deployment auf Dokploy
TestsJest 30, Unit-Tests des SERP-Parsers mit echten XML-Fixtures, E2E über supertest

Technische Entscheidungen

Atomare Kontingente per Redis-Lua. Ein manueller Check und der nächtliche Cronjob können um dasselbe Tageslimit eines Nutzers konkurrieren. Statt eines racy GET+SET prüft und reserviert ein per EVAL ausgeführtes Lua-Skript atomar; der Key ist an das Datum in der Zeitzone des Dienstes gebunden (TTL 25 h). Eine Überschreitung des Limits ist strukturell ausgeschlossen.

Einheitliche Zod-Verträge statt tRPC. Ein Schema in packages/shared erzeugt: Request-Validierung in NestJS (createZodDto), TypeScript-Typen und clientseitige Formularvalidierung. Frontend und Backend können beim Datenformat physisch nicht auseinanderlaufen — die API bleibt dabei reines REST und für jeden Client nutzbar.

BFF-Proxy mit transparentem Refresh. Tokens leben ausschließlich in httpOnly-Cookies — JavaScript sieht sie nie. Next.js-Route-Handler proxen Anfragen zu NestJS; bei einem 401 ruft der Proxy selbst /auth/refresh auf, mischt die aktualisierten set-cookie-Header in die wiederholte Anfrage und reicht sie an den Browser weiter. Der Nutzer bemerkt den Ablauf des Access-Tokens nicht.

Zwei-Engine-SERP-Client mit Behandlung von Eigenheiten. Yandex und Google verhalten sich über xmlstock unterschiedlich: Yandex liefert Top-100 in einem Request (Deep Grouping), Google begrenzt auf 10 Ergebnisse pro Seite — daher wurde Paginierung mit frühzeitigem Abbruch und Retries für den herstellerspezifischen "Code 110" implementiert. Fehler sind typisiert (SerpFetchError mit isRetryable-Flag) — die Retry-Policy der Queue richtet sich nach der Art des Fehlers statt pauschal alles zu wiederholen.

Rate Limiting auf drei Ebenen, jede mit eigener Aufgabe. Registrierungslimit pro IP (Redis, 3/Tag — zählt auch fehlgeschlagene Versuche, um Captcha-Brute-Force zu drosseln), Limiter des BullMQ-Workers (100 Jobs/s), In-Process-Token-Bucket für die kostenpflichtige SERP-API (20 Req/s gemäß den Limits von Yandex.XML).

Import, der echte Dateien übersteht. CSVs aus russischem Excel kommen oft als win1251: Der Parser erkennt defekte Dekodierung anhand von U+FFFD-Zeichen und liest die Datei erneut über iconv-lite ein; unterstützt werden die Trennzeichen ;/, sowie XLSX. Das Parsing-Ergebnis durchläuft dasselbe Zod-Schema wie die manuelle Eingabe.

Sicherheit von Haus aus. argon2id für Passwörter, Rotation der Refresh-Tokens, jede Datenabfrage wird auf Service-Ebene per userId gescoped (WHERE {id, userId} — IDOR ist architektonisch ausgeschlossen), Turnstile bei der Registrierung, in Redis ist der Default-Nutzer deaktiviert und durch einen ACL-Nutzer mit eingeschränkten Rechten ersetzt.

Prüfbare Pipeline. Parser-Unit-Tests laufen gegen echte, aufgezeichnete XML-Antworten beider Suchmaschinen; für jeden Pipeline-Schritt existiert ein Smoke-Skript (test-xmlstock, test-queue, test-processor); eine Admin-CLI (set-user-plan) ist im Produktions-Image enthalten und läuft direkt im Container über tsx.

Entwicklungsprozess

  • Spec-first: jeder Ausbauschritt der Pipeline (xmlstock-Client → Redis-Compose → Queue → Processor → HTTP-Trigger) begann mit einem Design-Dokument mit explizitem Scope / Goals / Non-Goals (docs/superpowers/specs/).
  • Schema-Evolution — 11 Prisma-Migrationen: vom Init über die Abschaffung serverseitiger Sessions zugunsten von stateless JWT, kaskadierende Löschungen, Anti-Abuse-Felder bis zur Verlagerung der Suchmaschine vom Projekt auf die Region (Unterstützung von Google innerhalb derselben Projekte).
  • CI/CD: ein Gate aus Lint + Tests → Matrix-Build der Docker-Images für API und Web → Push nach GHCR mit Layer-Caching und latest/sha/Tag-Tags → Deployment auf Dokploy. Der Produktions-Container der API wendet Migrationen selbst beim Start an (eine bewusste Single-Replica-Annahme, im Code dokumentiert).
  • Feature-Branches per git worktree, Merge über Pull Requests.

Roadmap

  • Abstraktion SearchProvider — Anbindung von DataForSeo / SerpApi als alternative SERP-Quellen.
  • Aggregation-Worker: Sichtbarkeits-Score, Trend der Durchschnittsposition, Zu-/Abnahmen auf Projektebene.
  • Abrechnung für den PAID-Tarif.
  • Verlaufsdiagramme der Positionen zusätzlich zur Pivot-Tabelle.
  • Prüfung einzelner Wörter
  • Datenexport nach XLSX und CSV
  • KI-generierte Kurzberichte zu den Tabellendaten für einen gewählten Zeitraum

Die Ergebnisse

50

parallele Worker in BullMQ

3

unabhängige Rate-Limiting-Ebenen

2

Kontingent-Reservierungsmodi

Screens