Zum Inhalt springen

Fallstudie

Klassifizierte Anzeigen für den Gebrauchtwagenmarkt

AvtoTochka verbindet private Verkäufer, Händler und Käufer über Inserate, VIN-Reports, Chat und Kreditanfragen — als installierbare PWA mit echten Web-Push-Benachrichtigungen.

Herausforderung

Der Markt für gebrauchte Fahrzeuge lebt von Vertrauen — und Vertrauen entsteht selten von selbst. Käufer:innen wissen nie sicher, ob die Kilometerangabe stimmt, ob das Fahrzeug einen Unfall hatte oder ob der Verkäufer tatsächlich der Halter ist. Verkäufer:innen wiederum konkurrieren gegen unzählige nahezu identische Inserate, in denen Preis, Zustand und Ausstattung nicht vergleichbar präsentiert werden.

Die zweite Hürde ist die Fahrzeughistorie. VIN-Reports existieren, sind aber verteilt, uneinheitlich strukturiert und oft erst nach dem Kauf verfügbar. Wer heute ein Fahrzeug prüft, muss zwischen mehreren Portalen wechseln, bezahlt mehrfach und bekommt trotzdem keine kohärente Sicht auf Kilometerstand, Halterwechsel oder Unfallhistorie.

Die dritte Hürde ist Finanzierung. Kredit, Versicherung und Inzahlungnahme sind für die Kaufentscheidung zentral, laufen aber traditionell ausserhalb des Inserats. Der Wechsel in eine externe Bankstrecke bricht den Kontext, verliert Käufer:innen unterwegs und macht die Konversion für Händler schwer messbar.

Die vierte Hürde ist technisch: ein Gebrauchtwagenportal muss schnell laden, auf jedem Gerät funktionieren, in Suchmaschinen sichtbar sein und Push-Nachrichten für neue Treffer zuverlässig zustellen — ohne dass ein grosses Team dahintersteht. Klassische Framework-Stacks lösen das mit viel JavaScript und noch mehr Betriebskosten. Wir wollten den anderen Weg gehen.

Ansatz

Fünf Grundsätze haben Architektur und Feature-Set geprägt:

  • Vertrauen als Produktebene — Bewertungen, Reaktionszeit, geprüfte Händler und VIN-Badges sind sichtbare Signale, keine versteckten Datenfelder. Käufer:innen sollen auf den ersten Blick erkennen, mit wem sie es zu tun haben.
  • Kein Framework, keine Magie — der Server läuft auf der Node.js-Standardbibliothek mit einem handgeschriebenen Router und Server-Side-Templates. Kein React, kein Next, kein Vue. Das hält den Auslieferungspfad kurz und die Betriebskosten niedrig.
  • Edge-Cache für anonyme Nutzung — die häufigsten Seiten liefert nginx aus dem Cache aus, während CSRF-Cookies für angemeldete Nutzer:innen intakt bleiben. Das erforderte gezielte nginx-Tuning statt einer weiteren Framework-Schicht.
  • Web Push ohne fremde Abhängigkeit — Push-Nachrichten sind in der Standardbibliothek implementiert (RFC 8291 aes128gcm + ES256 JWT), nicht über eine npm-Bibliothek. So bleibt der kritische Zustellpfad überschaubar und ohne Lieferketten-Risiko.
  • Partner-Integration hinter Feature-Flag — der Tinkoff-Kredit-Client ist vollständig verdrahtet, aber deaktiviert; die Aktivierung erfordert nur drei Zeilen .env und einen Container-Restart. So können Partner-Integrationen sicher landen, ohne live zu gehen, bevor die Verträge stehen.

Was wir gebaut haben

Katalog & Suche

Kaskade aus Marke → Modell → Generation → Motorisierung, ergänzt um Geo- und Städtewahl. Die Filter greifen serverseitig, damit auch der Erstaufruf ohne JavaScript funktioniert und Suchmaschinen die kompletten Listen indexieren.

Vertrauenssignale

Verkäuferbewertungen, Reaktionszeit-Badge, Verified-Partner-Badge für Händler und Verified-VIN-Badge für geprüfte Fahrzeuge. Jedes Signal ist im Datenmodell klar definiert und im UI konsistent platziert.

VIN-Report mit Paywall

Aggregierter Bericht zur Fahrzeughistorie mit Zahlschranke (299 ₽) und expliziter Rot-Flag-Darstellung. Auffälligkeiten — Kilometer-Regression, Halterwechselzahl, offene Verpflichtungen — stehen zuoberst, nicht im Kleingedruckten.

Händlerkabinett

Registrierung, Freigabe-Workflow durch das Admin und Organization-JSON-LD für Suchmaschinen. Händler pflegen Bestand, Kontaktzeiten und Firmenprofil selbst; die Freigabe stellt sicher, dass hinter dem Verified-Badge tatsächlich eine geprüfte Firma steht.

Monetarisierungs-Widgets

Kredit-, OSAGO- und Trade-in-Widgets liegen direkt auf der Inseratsseite. Anfragen laufen über /api/leads mit IP-basiertem Rate-Limit; der Admin sieht Leads gebündelt und kann als CSV exportieren.

Content-Hub

Sechs redaktionelle SEO-Artikel mit Markdown-Renderer und automatischer Marken-Verlinkung. Jeder Artikel trägt Article-, BreadcrumbList- und ItemList-JSON-LD, damit Suchmaschinen Struktur und Bezüge sauber aufnehmen.

PWA mit echtem Web Push

Installierbare Progressive Web App mit Service Worker, Install-Banner und Offline-Shell. Push-Benachrichtigungen laufen produktiv — GET /api/push/public-key liefert live 200 — und werden für neue Treffer und Nachrichten verwendet.

Admin & Betrieb

Admin-Übersicht für Leads, Freigaben und Inhalte. Betriebsseitig: stündliche SQLite-Snapshots mit 7 Tagen Aufbewahrung, rsync-sicheres Layout unter /opt/auto-tochka-ops/ und Deploy über Docker Compose hinter nginx.

Architektur

AvtoTochka ist bewusst einfach geschnitten: ein einzelner Node.js-Prozess bedient HTTP über einen handgeschriebenen Router, rendert Views als SSR-Strings und spricht direkt mit einer SQLite-Datenbank. Kein Framework, kein ORM, keine Build-Pipeline für den Server — was ausgeliefert wird, steht im Repository und läuft im Container unverändert.

Vor dem Anwendungscontainer sitzt nginx als Reverse-Proxy und Edge-Cache. Anonyme Aufrufe werden zwischengespeichert, während angemeldete Sitzungen den Cache umgehen. Damit das mit CSRF-Cookies zusammenspielt, wurde die $at_bypass-Map auf at_session verengt, proxy_ignore_headers Set-Cookie gesetzt und add_header always so korrigiert, dass Sicherheits-Header (CSP, HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy) den Cache überleben.

Web Push ist in der Standardbibliothek implementiert: RFC 8291 aes128gcm für den Payload, ES256 JWT für den VAPID-Header. Es gibt keine web-push-Abhängigkeit; der komplette Zustellpfad ist im eigenen Code sichtbar und testbar. VAPID-Schlüssel werden über Docker Compose env_file in den Container geführt.

Die Partner-Integration mit Tinkoff Credit ist als Sandbox-Client mit HMAC-Signierung auf Node-Standardbibliothek umgesetzt. Sie liegt hinter einem Feature-Flag: fehlen die Env-Schlüssel, fällt das System auf den bestehenden Lead-Flow zurück. Aktivierung ist ein bewusster, kleiner Schritt in der Produktions-.env.

Deployed wird über Docker Compose: der App-Container, nginx und die Backup-Routinen leben in einem versionierten Layout unter /opt/auto-tochka-ops/. Das Repository ist die Quelle der Wahrheit für docker-compose.yml (inklusive env_file und externen Netzwerken), damit rsync-Deploys keine Server-seitigen Änderungen überschreiben.

Zahlen & Fakten

MetrikWert
Tests122 / 122 grün
Lighthouse Mobile (Ø)Perf 97 · A11y 100 · BP 100 · SEO 100
Cumulative Layout Shift0
Render-Blocking (wastedMs)0 (kritisches CSS inline, style.css deferred)
Security-Header5 / 5 (CSP · HSTS · X-Frame-Options · Referrer-Policy · Permissions-Policy)
Edge-CacheHIT für anonyme Zweitaufrufe (nginx-tuning)
Sitemap~287 URLs (Katalog · 6 Artikel · Händler · VIN-Seiten)
Backupstündlicher SQLite-Snapshot, 7 Tage Aufbewahrung

Was wir gelernt haben

Edge-Cache und CSRF vertragen sich nur mit gezieltem nginx-Tuning. Solange irgendein Set-Cookie durchschlägt, umgeht der Cache alles. Erst mit einer engen $at_bypass-Map (nur at_session), proxy_ignore_headers Set-Cookie und einem sauberen add_header always blieben Cache-HITs und Sicherheits-Header gleichzeitig erhalten.

Web Push auf der Standardbibliothek ist machbar — und lohnt sich. RFC 8291 aes128gcm und ES256 JWT sind nicht trivial, aber überschaubar. Der Verzicht auf web-push entfernt eine Lieferketten-Abhängigkeit aus dem kritischsten Zustellpfad und macht Fehler nachvollziehbar. Aufwändig war weniger die Kryptografie als das Durchreichen der VAPID-Schlüssel durch Docker Compose; erst nachdem env_file sauber deklariert war, sah der Container mehr als ADMIN_KEY und PORT.

Rsync-Deploys brauchen eine klare Quelle der Wahrheit. Solange der Server eine eigene Compose-Datei pflegte, überschrieb jeder Deploy die Infra-Konfiguration. Nachdem das Repository verbindlich wurde (env_file und externe Netzwerke deklariert), verschwand die Klasse Fehler komplett.

Ehrliche Grenzen sind besser als schöngerechnete Metriken. Beim LCP-Tuning erreichten wir Render-Blocking 0 ms, Hero-Preload und Font-Preload — aber der LCP-Kandidat ist ein Textabsatz, dessen Paint-Zeitpunkt sich nicht weiter frontendseitig verkürzen lässt. Wir haben aufgehört zu tunen, statt eine Zahl zu erfinden.

Nächste Schritte

  • Capacitor-Wrap für App-Store-Präsenz — die PWA ist installierbar, ein nativer Wrap ist die naheliegende nächste Ausbaustufe.
  • Aktivierung der Tinkoff-Kredit-Integration in Produktion sobald die Partnerverträge stehen — technisch sind es drei Zeilen .env und docker compose restart app.
  • Ausbau des Content-Hubs entlang tatsächlicher Suchnachfrage: mehr Ratgeber-Artikel, tiefere Marken- und Modellseiten, verlinkte VIN-Erklärungen.

Verwandt

Zusammenarbeit

Ähnliche Arbeit im Sinn?

Lassen Sie uns kurz sprechen — kein Verkaufsgespräch.