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:
- Aufbau einer fehlertoleranten, asynchronen Pipeline zur Datenerfassung (SERP), die Restriktionen und Eigenheiten (Quirks) externer APIs umgehen kann.
- Vermeidung von Race Conditions bei der Berechnung von Nutzerkontingenten (FREE/PAID-Tarife) zwischen dem Planer (Scheduler) und manuellen Starts.
- Gewährleistung einer durchgehenden Typsicherheit (End-to-End Type Safety) zwischen den unabhängigen Frontend- und Backend-Anwendungen ohne Code-Duplizierung.
- 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
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.
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.
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.
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.
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:
- Trigger — täglicher Cronjob oder manueller Start aus der UI.
- Auswahl — der Scheduler reiht nur Keywords ein, deren letzter Check älter ist als das
checkIntervaldes Projekts. - 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 mitused/limit-Details) undreserveUpTo(best-effort für den nächtlichen Cronjob — nimmt, was übrig ist). - Queue — Jobs erhalten 3 Versuche mit exponentiellem Backoff; wiederholt wird nur, was als retryable markiert ist.
- 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.
- Parsing und Speichern — defensives XML-Parsing ohne
any, Domain-Normalisierung (Protokoll/www/Port/Pfad), Speichern einesPositionCheck-Datensatzes (null= Domain nicht in den Top-Ergebnissen).
Datenmodell
Technologie-Stack
| Schicht | Technologien |
|---|---|
| Backend | NestJS 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) |
| Frontend | Next.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) |
| Import | csv-parse, SheetJS (xlsx), iconv-lite |
| Infra | Turborepo, Docker Multi-Stage (turbo prune --docker, node:22-alpine, non-root), docker-compose (Postgres + Redis mit ACL), GitHub Actions → GHCR, Deployment auf Dokploy |
| Tests | Jest 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
parallele Worker in BullMQ
unabhängige Rate-Limiting-Ebenen
Kontingent-Reservierungsmodi