
Jahrelang war Vercel bei WebCatalog die Standardplattform, auf der wir fast alles für das Web bereitstellten. Die meisten dieser Projekte nutzten Next.js, daher war die Kombination naheliegend. Unsere Marketing-Websites, Produktoberflächen, Kontoeinstellungen, Entwicklerkonsole, Authentifizierung, Admin-Tools und APIs landeten alle auf ungefähr demselben Stack.
Das funktionierte lange Zeit gut. Vercel machte Deployments einfach, Next.js bot uns ein produktives Full-Stack-Framework, und über die Infrastruktur darunter mussten wir uns kaum Gedanken machen.
Irgendwann passte dieser Komfort nicht mehr zu unseren Produkten.
Unsere Marketing-Websites profitierten weiterhin von SSR (serverseitigem Rendering), die meisten anderen Web-Apps aber nicht. Sie konnten einfache SPAs (Single-Page-Anwendungen) sein. Unsere APIs brauchten eine normale Node.js-Umgebung mit nativen Abhängigkeiten und langlebigen Prozessen. Fast alles andere in unserem Frontend-Stack nutzte bereits Vite. Gleichzeitig ließen sich die Infrastrukturkosten immer schwerer ignorieren, insbesondere als der öffentliche und automatisierte Traffic zunahm.
Zunächst versuchten wir, das Bestehende zu optimieren. Wir erwogen, Next.js beizubehalten und es über einen Adapter auf Cloudflare Workers zu verlagern. Für die Marketing-Websites probierten wir Astro aus. Unsere APIs betrieben wir kurzzeitig auf Cloudflare Containers.
Am Ende landeten wir bei einer einfacheren Lösung: Die meisten unserer Web-Apps nutzen jetzt Vite + TanStack Router auf Cloudflare Workers, unsere Marketing-Websites TanStack Start mit SSR auf Cloudflare Workers und unsere APIs Hono + tRPC auf Railway als langlebige Node.js-Container.
Durch die Migration sanken unsere Web-Infrastrukturkosten um etwa 80 %. Nachdem wir zusätzlich unsere Cloudflare-Sicherheitsregeln verschärft und mehr feindseligen automatisierten Traffic am Edge blockiert hatten, stieg die Gesamtersparnis auf ungefähr 90 %.
Auch der Großteil der Migrationsarbeit wurde von KI-Coding-Agenten ausgeführt, vor allem von Claude Fable 5 und Claude Opus 5. Unsere Engineers entschieden über die Architektur, definierten Rahmenbedingungen, prüften Änderungen und verifizierten das Ergebnis.
Wir wollten Vercel zunächst nicht verlassen
Unser erster Impuls war, das bestehende Setup zu optimieren.
webcatalog.io war der naheliegendste Ausgangspunkt: Die Website erhält viel öffentlichen Traffic, während sich die meisten ihrer Seiten vergleichsweise selten ändern. Wir stellten fest, dass noch zu viel dynamisch gerendert wurde. Deshalb beseitigten wir unbeabsichtigtes dynamisches Rendering, ergänzten ISR (Incremental Static Regeneration) für stark frequentierte Routen, machten mehr Seiten cachebar und optimierten aufwendige Vorgänge rund um die Sitemap.
Das half, machte aber auch das eigentliche Problem deutlicher. Wir verbrachten immer mehr Zeit damit herauszufinden, warum relativ statische Inhalte dynamisch waren, welches Framework-Verhalten dafür verantwortlich war und welchen Framework-Mechanismus wir einsetzen mussten, um sie wieder cachebar zu machen.
Viele unserer anderen Anwendungen hatten unterdessen das gegenteilige Problem. Kontoeinstellungen, Entwicklerkonsole, Admin-Anwendungen und die meisten Produktoberflächen brauchten überhaupt kein serverseitiges Rendering. Es waren Browseranwendungen, die mit APIs kommunizierten.
Ab diesem Punkt lautete die Frage nicht mehr: „Wie senken wir unsere Vercel-Rechnung?“, sondern: „Was würden wir bauen, wenn diese Anwendungen nicht bereits Next.js-Anwendungen wären?“
Wir erwogen Next.js auf Cloudflare
Die Option mit den geringsten Umstellungen war, Next.js beizubehalten und von Vercel zu Cloudflare Workers zu verlagern.
Wir zogen das ernsthaft in Betracht, denn Next.js kann über Adapter auf Cloudflare laufen. So hätten wir einen Großteil der bestehenden Anwendungsstruktur bewahren können.
Next.js auf Cloudflare betrachteten wir allerdings nicht als gleichwertig mit Next.js auf Vercel. Next.js ist am engsten mit der Runtime, dem Deployment-System, dem Caching-Verhalten und den Plattformfunktionen von Vercel integriert. Auf Cloudflare muss ein Adapter diese Annahmen auf eine andere Runtime übertragen.
Das kann gut funktionieren. Wenn unser Ziel aber ein einfacherer Stack war, erschien uns eine zusätzliche Kompatibilitätsschicht nicht ideal.
Hinzu kam eine grundsätzliche Frage der Tools. Fast alles andere, was wir im Frontend entwickelten, nutzte bereits Vite: Desktop-Anwendungen, Browsererweiterungen, SPAs und andere clientseitige Projekte.
Next.js hatte sich stark in Richtung Turbopack bewegt, und Turbopack hat sich erheblich verbessert. Es ging uns nicht um Performance. Der größere Unterschied lag für uns im Ökosystem. Vite war bereits die gemeinsame Grundlage für den Großteil unserer Frontend-Codebasis, mit einem breiteren Plugin-Ökosystem und mehr projektübergreifend wiederverwendbarer Konfiguration.
Next.js auf Cloudflare zu behalten hätte also einen Teil des Hosting-Problems gelöst, aber sowohl die Diskrepanz zwischen den Frameworks als auch ein separates Build-Tool-Ökosystem erhalten.
Deshalb traten wir einen Schritt zurück und sahen uns an, was die einzelnen Workloads tatsächlich brauchten.
Wir teilten den Stack nach Workload auf
Als wir aufhörten, nach einem einzigen universellen Ersatz für Next.js zu suchen, wurde die Architektur deutlich einfacher.
Unsere Marketing-Websites brauchten SSR, weil sie öffentliche, lokalisierte Seiten, Metadaten, kanonische URLs und Sitemaps haben und Traffic über Suchmaschinen erhalten.
Die meisten anderen Web-Apps brauchten kein SSR und konnten einfach Vite-Anwendungen mit TanStack Router sein.
Unsere APIs brauchten überhaupt kein React-Framework und waren in einer normalen Node.js-Runtime besser aufgehoben.
Die endgültige Aufteilung sah so aus:
Web-Apps
Vite + TanStack Router
│
└── Cloudflare Workers
Marketing-Websites
TanStack Start + SSR
│
└── Cloudflare Workers
APIs
Hono + tRPC
│
└── Railway / Node.js-Container
Die Regel war damit einfach: SSR dort einsetzen, wo es echten Mehrwert bietet, andernorts eine einfache SPA verwenden und Backend-Dienste in einer Backend-Runtime betreiben.
Die meisten Web-Apps wurden zu SPAs
Die SPA-Migrationen waren der einfachste Teil.
Die WebCatalog-Web-App war besonders schnell umgestellt: Wir erstellten das Grundgerüst für den Ersatz mit Vite + TanStack Router, portierten die Oberfläche, stellten das Deployment auf Cloudflare Workers um und entfernten die alte Next.js-Version – alles am selben Vormittag.
Dasselbe Muster wiederholten wir für die Lexibird-Web-App, die Kontoeinstellungen, unsere Entwicklerkonsole, die Authentifizierung und die Admin-Anwendungen. Vom ersten SPA-Grundgerüst bis zum Löschen der letzten Next.js-Anwendung vergingen etwa 19 Tage.
Für diese Anwendungen ist der Einsatz von Cloudflare Workers bewusst unspektakulär. Vite baut statische Assets, Cloudflare liefert sie aus, und Anwendungsrouten fallen auf index.html zurück. Es gibt keine SSR-Runtime, weil nichts serverseitig gerendert werden muss.
Schwieriger war es, das Verhalten zu erhalten, das sich im Laufe der Zeit um diese Anwendungen herum angesammelt hatte. Ein Teil der Authentifizierungslogik musste aus den Next.js-Serverrouten in die API verlagert werden, und ältere Versionen der WebCatalog-Desktop-App waren weiterhin auf Legacy-Endpunkte angewiesen, die funktionsfähig bleiben mussten.
Die React-Oberfläche zu verlagern war einfach.
Die bestehenden Schnittstellenverträge zu bewahren war schwieriger.
Wir probierten Astro aus und löschten es fünf Tage später wieder
Die Marketing-Websites waren anspruchsvoller.
Unsere erste Wahl war Astro. Für webcatalog.io und lexibird.com schien es naheliegend, weil beide öffentlich zugängliche, inhaltsreiche Websites sind und Astro sich gut in Cloudflare integriert.
Wir begannen, beide Websites zu portieren, und hörten fünf Tage später wieder auf.
Es gab keinen einzelnen K.-o.-Grund. Stattdessen summierten sich kleinere Unstimmigkeiten. Manche Routen brauchten beim Prerendering Datenbankzugriff, während unsere Build-Umgebung bewusst keine Zugangsdaten zur Produktionsdatenbank hatte. React Islands erschwerten die gemeinsame Nutzung von Anwendungszustand. Außerdem stießen wir auf Unterschiede zwischen vorgerenderten und zur Laufzeit gerenderten Routen sowie auf einige Reibungsverluste bei den Tools in unserem Repository.
Keines dieser Probleme war unlösbar. Genau deshalb wäre es gefährlich gewesen weiterzumachen. Wir hätten leicht noch einige Wochen damit verbringen können, ein Problem nach dem anderen zu beheben.
Stattdessen löschten wir die Implementierung und fingen von vorn an.
KI-Agenten veränderten die wirtschaftliche Abwägung dieser Entscheidung. Ein Großteil der mechanischen Portierungsarbeit hatte keine Wochen an Arbeitszeit unserer Engineers beansprucht. Die bereits investierten Kosten waren also geringer, und es fiel leichter einzugestehen, dass wir eine andere Architektur bevorzugten.
TanStack Start passte besser
Wir starteten die Migration der Marketing-Websites erneut, ausgehend vom ursprünglichen Next.js-Code – diesmal mit TanStack Start.
Das passte deutlich besser, weil wir TanStack Router bereits in den SPA-Anwendungen nutzten. Routing, Loader, Suchparameter und Navigation folgten damit ähnlichen Konzepten. Der Code blieb gewöhnliches React und TypeScript, und als Build-System diente Vite.
Wir portierten lexibird.com an einem Tag auf TanStack Start. webcatalog.io folgte.
Der Vorteil war nicht, dass TanStack Start jedes Problem wie von Zauberhand löste. Es passte vielmehr zu der Richtung, in die sich unser übriger Stack bereits entwickelte.
In einem Monorepo ist das wichtig. Gemeinsame Build-Tools bedeuten mehr wiederverwendbare Plugins und Konfiguration, weniger Sonderfälle beim Debugging, weniger Kontextwechsel für Entwickler und weniger projektspezifische Konventionen, die Coding-Agenten erst entdecken müssen.
Cloudflare war für unseren Traffic deutlich günstiger
Die Architektur war nur ein Teil des Grundes für die gesunkene Rechnung. Die Preise von Cloudflare waren ein wesentlicher Faktor, insbesondere bei der Bandbreite.
Beim kostenpflichtigen Cloudflare-Workers-Tarif richten sich die Kosten derzeit hauptsächlich nach Anfragen und CPU-Nutzung; für Workers fallen keine zusätzlichen Kosten für Datenübertragung oder Bandbreite an. (developers.cloudflare.com) Für öffentliche Websites macht das viel aus: Eine Anfrage kann kaum CPU-Leistung beanspruchen und trotzdem HTML, JavaScript, Bilder, Schriftarten und andere Assets übertragen.
Auch das Preismodell von Vercel umfasst netzwerkbezogene Nutzungskategorien und Inklusivkontingente, darunter Fast Data Transfer und Fast Origin Transfer. (vercel.com) Für unser Traffic-Profil war Cloudflare wirtschaftlich deutlich vorteilhafter.
Die Einsparungen entstanden nicht allein durch den Anbieterwechsel. Wir stellten auch viele Anwendungen auf statische Vite-Builds um, reduzierten unnötiges SSR, machten das Caching expliziter und verlagerten API-Workloads in eine besser passende Runtime. Die Preise von Cloudflare, insbesondere für Bandbreite, trugen aber wesentlich zu der beobachteten Reduktion um etwa 80 % bei.
Diese Zahl gilt für unseren Workload. Sie bedeutet nicht, dass jede Anwendung beim Wechsel von Vercel zu Cloudflare 80 % spart.
Die APIs landeten auf Railway
Die APIs waren ein separates Problem.
Obwohl sie Next.js-Projekte waren, waren sie bereits weitgehend unabhängig von Next.js. Die WebCatalog-API umfasste rund 34.000 Codezeilen, aber nur sieben Dateien importierten next/server. tRPC nutzte bereits seinen Fetch-Adapter, und Inngest unterstützte Hono.
Wir ersetzten die Next.js-HTTP-Schicht durch Hono. Dieser Teil war überraschend klein.
Schwieriger war die Frage, wo die APIs laufen sollten. Einfache Workers eigneten sich nicht gut, weil die APIs Node.js und native Abhängigkeiten wie sharp verwenden.
Zunächst probierten wir Cloudflare Containers aus. Damit kamen wir schnell von Vercel weg. Nachdem das Setup eine Zeit lang produktiv gelaufen war, stellten wir jedoch fest, dass sein Betriebsmodell mehr manuelle Anpassungen der Kapazität erforderte, als wir damals wollten.
Also verlagerten wir die APIs erneut.
Heute laufen sie auf Railway als gewöhnliche, dauerhaft laufende Node.js-Containerdienste. CI (Continuous Integration) erstellt Docker-Images und lädt sie in unsere Registry hoch; Railway übernimmt die horizontale Autoskalierung.
Nicht alles muss am Edge laufen.
Ohne Vercel mussten wir mehr selbst verantworten
Die niedrigeren Kosten gingen mit einem Kompromiss einher.
Vercel und Next.js hatten viele Infrastrukturaufgaben auf einer höheren Ebene übernommen. Mit TanStack Start und Workers setzten wir stattdessen stärker auf explizites HTTP-Caching. Eine Seite wird gerendert, wir senden Cache-Header zurück, und Cloudflare speichert die Antwort im Cache. Für Browser- und Edge-Caching können unterschiedliche Regeln gelten, einschließlich Stale-While-Revalidate-Verhalten am Edge.
Uns gefiel, dass diese Entscheidungen nun ausdrücklich getroffen wurden. Eine explizit konfigurierte Infrastruktur bedeutet aber auch, dass Fehler in unserer Verantwortung liegen.
Ein paar Fehler machten wir. Eine Cache-Regel in der Produktionsumgebung erfasste versehentlich besucherspezifische Antworten von TanStack-Serverfunktionen. Eine andere Cache-Konfiguration vertrug sich nicht mit dem SPA-Fallback-Verhalten. Diese Bugs erinnerten uns daran, dass sich die Korrektheit einer Migration nicht allein anhand des Repositorys nachweisen lässt. Auch die produktive Infrastruktur ist Teil des Systems.
KI-Agenten veränderten unsere Vorgehensweise bei der Migration
Den Großteil der Implementierungsarbeit erledigten Coding-Agenten, hauptsächlich Claude Fable 5 und Claude Opus 5.
Framework-Migrationen eignen sich ungewöhnlich gut für Agenten, weil für einen Großteil der Arbeit eine klare bestehende Referenz vorliegt: Diese Route verlagern, diese URL erhalten, diese Framework-API ersetzen, dieselben Metadaten beibehalten, den Typecheck ausführen, Fehler beheben und das Ergebnis mit der alten Implementierung vergleichen.
Für webcatalog.io führten wir einen Migrationstracker, der die Arbeit in Phasen wie Grundlagen, Produktseiten, Katalogseiten, Suche, Blog, Preise, Weiterleitungen, Sitemaps und Umstellung gliederte. Statt einen Agenten aufzufordern, „webcatalog.io zu TanStack Start zu migrieren“, gaben wir ihm klar abgegrenzte Aufgaben mit ausdrücklich festgelegten Anforderungen.
Die bestehende Anwendung wurde zur Spezifikation. Die Aufgabe des Agenten war, ihr Verhalten mit der neuen Architektur nachzubilden, nicht das Produkt neu zu erfinden.
Dadurch verlagerte sich die Arbeit der Menschen weg von der manuellen Codeübertragung und hin zu Fragen wie: Sollte diese Seite überhaupt SSR nutzen? Welches Verhalten ist beabsichtigt? Was darf global gecacht werden? Welche URLs müssen unverändert bleiben? Wo sollte die Authentifizierung stattfinden? Wann sollten wir aufhören, einen Ansatz zum Funktionieren bringen zu wollen?
Wir prüften die Ergebnisse weiterhin, führten Builds und Typechecks aus und kontrollierten besonders risikoreiche Bereiche wie Authentifizierung, SEO (Suchmaschinenoptimierung), Weiterleitungen und Caching manuell.
Die entscheidende Veränderung war nicht, dass KI für uns Code schrieb. Sie machte Experimente günstiger.
Astro auszuprobieren und nach fünf Tagen wieder zu löschen ließ sich dadurch viel leichter rechtfertigen. Auch die APIs erst einmal zu verlagern und dann zu entscheiden, dass eine andere Plattform besser passte, war weniger schmerzhaft. Die mechanische Arbeit war günstiger, sodass wir die Richtung ändern konnten, wenn die Architektur nicht stimmte – statt nur deshalb weiterzumachen, weil wir bereits zu viel investiert hatten.
Agenten machten Implementierungsarbeit weniger knapp.
Dadurch wurden Architektur, Rahmenbedingungen, Review und Verifizierung wichtiger.
Aus 80 % wurden ungefähr 90 %
Nach der Migration waren unsere Web-Infrastrukturkosten etwa 80 % niedriger.
Dann begannen wir genauer darauf zu achten, welche Anfragen unsere Anwendungen überhaupt erreichten.
Ein erheblicher Teil des öffentlichen Internet-Traffics ist automatisiert: Suchmaschinen, KI-Crawler, Agenten, Scraper, Scanner und weniger freundliche Bots. Ein Teil dieses Traffics ist nützlich, ein anderer nicht.
Da die Anwendungen nun direkt hinter Cloudflare lagen, verschärften wir unsere Sicherheitsregeln. So konnte feindseliger automatisierter Traffic bereits am Edge abgewiesen werden, bevor er Rechenaufwand in der Anwendung oder Datenbankabfragen auslöste.
Danach erreichte unsere Gesamtersparnis gegenüber dem alten Setup ungefähr 90 %.
Für die endgültige Reduktion wirkten mehrere Faktoren zusammen: die niedrigeren Preise von Cloudflare, besonders bei der Bandbreite; weniger serverseitige Arbeit; eine besser geeignete Runtime für unsere APIs; expliziteres Caching; und weniger unnötige Anfragen, die unsere Anwendungen überhaupt erreichten.
Das Ergebnis
In unserem Repository gibt es keine Next.js-Anwendungen mehr. Die meisten unserer Web-Apps sind jetzt Vite + TanStack Router SPAs auf Cloudflare Workers. webcatalog.io und lexibird.com nutzen TanStack Start auf Cloudflare Workers, weil diese Websites tatsächlich von SSR profitieren. Unsere APIs laufen mit Hono + tRPC auf Railway als gewöhnliche Node.js-Container. Fast alle unsere Frontend-Projekte gehören nun zum Vite-Ökosystem.
Unsere Infrastrukturkosten sanken um etwa 80 %. Dazu trugen die für unseren Traffic deutlich günstigeren Konditionen von Cloudflare, insbesondere bei der Bandbreite, wesentlich bei. Das Herausfiltern feindseligen automatisierten Traffics am Edge erhöhte die Gesamtersparnis auf ungefähr 90 %.
Am wichtigsten ist uns aber das architektonische Ergebnis. Marketing-Websites bekommen SSR, alles andere, was eine SPA sein kann, ist eine SPA, und APIs laufen auf echten Servern, wenn echte Server die einfachere Lösung sind.
Anfangs wollten wir Vercel günstiger machen.
Am Ende erkannten wir, dass wir einen Großteil der Architektur, für die wir bezahlten, gar nicht brauchten.