
Przez lata Vercel był domyślną platformą, na której wdrażaliśmy w WebCatalog niemal wszystko, co działało w sieci. Większość tych projektów korzystała z Next.js, więc było to naturalne połączenie. Nasze strony marketingowe, interfejsy produktów, ustawienia konta, konsola deweloperska, uwierzytelnianie, narzędzia administracyjne i API działały ostatecznie na mniej więcej tym samym stosie technologicznym.
Przez długi czas sprawdzało się to dobrze. Vercel upraszczał wdrożenia, Next.js dawał nam wydajny framework full-stack, a my rzadko musieliśmy myśleć o infrastrukturze, na której to wszystko działało.
Z czasem ta wygoda przestała odpowiadać potrzebom naszych produktów.
Nasze strony marketingowe nadal korzystały z zalet SSR (renderowania po stronie serwera), ale większość pozostałych aplikacji webowych go nie potrzebowała. Mogły być zwykłymi SPA (aplikacjami jednostronicowymi). Nasze API potrzebowały standardowego środowiska Node.js z natywnymi zależnościami i długo działającymi procesami. Niemal cała pozostała część naszego stosu frontendowego korzystała już z Vite. Jednocześnie koszty infrastruktury stawały się coraz trudniejsze do zignorowania, zwłaszcza w miarę wzrostu ruchu publicznego i automatycznego.
Najpierw próbowaliśmy zoptymalizować to, co już mieliśmy. Rozważaliśmy pozostawienie Next.js i przeniesienie go do Cloudflare Workers za pomocą adaptera. Wypróbowaliśmy Astro dla stron marketingowych. Na krótko umieściliśmy nasze API w Cloudflare Containers.
Ostatecznie doszliśmy do prostszego rozwiązania: większość naszych aplikacji webowych korzysta teraz z Vite + TanStack Router na Cloudflare Workers, strony marketingowe używają TanStack Start z SSR na Cloudflare Workers, a API działają na Hono + tRPC na Railway jako długo działające kontenery Node.js.
Migracja obniżyła koszty naszej infrastruktury webowej o około 80%. Gdy dodatkowo zaostrzyliśmy reguły bezpieczeństwa Cloudflare i zaczęliśmy blokować więcej szkodliwego ruchu automatycznego na brzegu sieci, łączne oszczędności wzrosły do około 90%.
Większość prac wdrożeniowych związanych z migracją wykonali też agenci AI do programowania, przede wszystkim Claude Fable 5 i Claude Opus 5. Inżynierowie podejmowali decyzje architektoniczne, określali ograniczenia, przeglądali zmiany i weryfikowali wynik.
Nie zaczęliśmy od odejścia z Vercel
Naszym pierwszym odruchem była optymalizacja istniejącego rozwiązania.
Najbardziej oczywistym punktem wyjścia był webcatalog.io, ponieważ notuje duży ruch publiczny, choć większość jego stron zmienia się stosunkowo rzadko. Odkryliśmy, że zbyt duża część witryny nadal była renderowana dynamicznie. Wyeliminowaliśmy więc niezamierzone renderowanie dynamiczne, dodaliśmy ISR (Incremental Static Regeneration) do często odwiedzanych ścieżek, umożliwiliśmy buforowanie większej liczby stron i zoptymalizowaliśmy kosztowne generowanie map witryny.
Te działania pomogły, ale jednocześnie wyraźniej pokazały zasadniczy problem. Poświęcaliśmy coraz więcej czasu na ustalanie, dlaczego stosunkowo statyczne treści były dynamiczne, które zachowanie frameworka za to odpowiadało i z jakiego mechanizmu frameworka powinniśmy skorzystać, by znów umożliwić ich buforowanie.
Tymczasem wiele naszych pozostałych aplikacji miało odwrotny problem. Ustawienia konta, konsola deweloperska, aplikacje administracyjne i większość interfejsów produktów w ogóle nie potrzebowały renderowania po stronie serwera. Były aplikacjami działającymi w przeglądarce i komunikującymi się z API.
W tym momencie przestaliśmy pytać: „Jak obniżyć rachunek za Vercel?”, a zaczęliśmy: „Co zbudowalibyśmy, gdyby te aplikacje nie były już napisane w Next.js?”.
Rozważaliśmy Next.js na Cloudflare
Najmniej rewolucyjną opcją było zachowanie Next.js i przeniesienie go z Vercel do Cloudflare Workers.
Poważnie to rozważaliśmy, ponieważ Next.js może działać na Cloudflare dzięki warstwom adapterów. Pozwoliłoby nam to zachować znaczną część istniejącej struktury aplikacji.
Nie uważaliśmy jednak Next.js na Cloudflare za odpowiednik Next.js na Vercel. Next.js jest najściślej zintegrowany ze środowiskiem uruchomieniowym Vercel, jego systemem wdrożeń, sposobem buforowania i funkcjami platformy. Na Cloudflare adapter musi przełożyć te założenia na inne środowisko uruchomieniowe.
To może działać dobrze, ale jeśli naszym celem było uproszczenie stosu, dodawanie kolejnej warstwy zgodności nie wydawało się idealne.
Istniała też szersza kwestia narzędzi. Niemal wszystko, co budowaliśmy po stronie frontendu, korzystało już z Vite: aplikacje desktopowe, rozszerzenia przeglądarek, SPA i inne projekty klienckie.
Next.js wyraźnie skierował się ku Turbopack, który bardzo się rozwinął. Nie chodziło nam o wydajność. Ważniejsza była dla nas różnica między ekosystemami. Vite stanowił już wspólną podstawę większości naszego kodu frontendowego, oferując szerszy ekosystem wtyczek i więcej konfiguracji, którą można wykorzystywać ponownie w różnych projektach.
Zachowanie Next.js na Cloudflare rozwiązałoby więc część problemu z hostingiem, ale utrzymałoby zarówno niedopasowanie frameworka, jak i osobny ekosystem narzędzi do budowania aplikacji.
Cofnęliśmy się zatem o krok i przyjrzeliśmy się rzeczywistym potrzebom poszczególnych aplikacji i usług.
Podzieliliśmy stos według potrzeb
Gdy przestaliśmy szukać jednego uniwersalnego zamiennika Next.js, architektura stała się znacznie prostsza.
Nasze strony marketingowe potrzebowały SSR, ponieważ mają publiczne strony w wielu wersjach językowych, metadane, adresy kanoniczne, mapy witryn i ruch z wyszukiwarek.
Większość pozostałych aplikacji webowych nie potrzebowała SSR i mogła po prostu działać jako aplikacje Vite z TanStack Router.
Nasze API w ogóle nie potrzebowały frameworka React i lepiej pasowało do nich standardowe środowisko uruchomieniowe Node.js.
Ostateczny podział wyglądał tak:
Aplikacje webowe
Vite + TanStack Router
│
└── Cloudflare Workers
Strony marketingowe
TanStack Start + SSR
│
└── Cloudflare Workers
API
Hono + tRPC
│
└── Railway / kontenery Node.js
Zasada stała się prosta: używaj SSR tam, gdzie daje realną wartość, zwykłego SPA tam, gdzie SSR jej nie daje, a usługi backendowe uruchamiaj w środowisku backendowym.
Większość aplikacji webowych stała się SPA
Migracja aplikacji do SPA była najłatwiejsza.
Aplikację webową WebCatalog przenieśliśmy szczególnie szybko: przygotowaliśmy szkielet zamiennika opartego na Vite + TanStack Router, przenieśliśmy interfejs, przełączyliśmy wdrożenie na Cloudflare Workers i usunęliśmy starą wersję Next.js — wszystko w ciągu jednego poranka.
Ten sam schemat zastosowaliśmy do aplikacji webowej Lexibird, ustawień konta, konsoli deweloperskiej, uwierzytelniania i aplikacji administracyjnych. Od przygotowania pierwszego szkieletu SPA do usunięcia ostatniej aplikacji Next.js minęło około 19 dni.
W przypadku tych aplikacji Cloudflare Workers celowo pełni prostą rolę. Vite buduje zasoby statyczne, Cloudflare je udostępnia, a ścieżki aplikacji kierowane są do index.html. Nie ma środowiska uruchomieniowego SSR, ponieważ nic nie wymaga renderowania po stronie serwera.
Trudniejsze było zachowanie funkcjonalności, które z czasem narosły wokół tych aplikacji. Część logiki uwierzytelniania musieliśmy przenieść z tras serwerowych Next.js do API, a starsze wersje aplikacji desktopowej WebCatalog nadal korzystały z dawnych endpointów, których działanie musieliśmy zachować.
Przeniesienie interfejsu React było łatwe.
Zachowanie istniejących kontraktów — trudniejsze.
Wypróbowaliśmy Astro i usunęliśmy je pięć dni później
Strony marketingowe były trudniejsze.
Naszym pierwszym wyborem było Astro, które wydawało się naturalnym rozwiązaniem dla webcatalog.io i lexibird.com: obie witryny są publiczne, zawierają dużo treści, a Astro dobrze integruje się z Cloudflare.
Zaczęliśmy przenosić obie witryny, ale po pięciu dniach przerwaliśmy prace.
Nie było jednej decydującej wady. Zamiast tego gromadziły się drobne niedopasowania. Niektóre ścieżki wymagały dostępu do bazy danych podczas wstępnego renderowania, podczas gdy nasze środowisko budowania celowo nie miało danych uwierzytelniających do produkcyjnej bazy. Wyspy React utrudniały naturalne współdzielenie stanu aplikacji. Natrafiliśmy też na różnice między ścieżkami renderowanymi wstępnie a tymi renderowanymi w czasie działania oraz na pewne tarcia związane z narzędziami w naszym repozytorium.
Żaden z tych problemów nie był niemożliwy do rozwiązania. Właśnie dlatego dalsze prace były ryzykowne. Moglibyśmy z łatwością spędzić kolejne tygodnie na rozwiązywaniu problemu za problemem.
Zamiast tego usunęliśmy tę implementację i zaczęliśmy od nowa.
Agenci AI zmienili rachunek ekonomiczny tej decyzji. Duża część mechanicznej pracy przy przenoszeniu kodu nie pochłonęła tygodni pracy inżynierów, więc koszt już poniesionych prac był mniejszy. Łatwiej było przyznać, że wolimy inną architekturę.
TanStack Start pasował lepiej
Ponownie rozpoczęliśmy migrację stron marketingowych, wychodząc od oryginalnego kodu Next.js, tym razem z użyciem TanStack Start.
Pasował znacznie naturalniej, ponieważ w aplikacjach SPA korzystaliśmy już z TanStack Router. Routing, loadery, parametry wyszukiwania i nawigacja opierały się więc na podobnych koncepcjach. Kod pozostał zwykłym kodem React i TypeScript, a systemem budowania był Vite.
Przenieśliśmy lexibird.com na TanStack Start w jeden dzień. Potem przyszła kolej na webcatalog.io.
Zaletą nie było to, że TanStack Start w magiczny sposób rozwiązał każdy problem. Chodziło o to, że pasował do kierunku, w którym zmierzała reszta naszego stosu.
W monorepo ma to znaczenie. Wspólne narzędzia do budowania oznaczają więcej wtyczek i konfiguracji, które można wykorzystywać ponownie, mniej szczególnych przypadków podczas debugowania, rzadsze przełączanie się między kontekstami przez programistów i mniej konwencji specyficznych dla projektu, które agenci programistyczni muszą odkrywać.
Cloudflare był znacznie tańszy przy naszym ruchu
Architektura była tylko jednym z powodów, dla których nasze rachunki spadły. Duże znaczenie miały ceny Cloudflare, zwłaszcza opłaty za transfer danych.
Płatny plan Cloudflare Workers nalicza obecnie opłaty przede wszystkim za żądania i wykorzystanie CPU, bez dodatkowych opłat za transfer danych lub przepustowość w Workers. (developers.cloudflare.com) Ma to ogromne znaczenie dla publicznych witryn: żądanie może zużywać bardzo mało czasu CPU, a mimo to przesyłać HTML, JavaScript, obrazy, czcionki i inne zasoby.
Model cenowy Vercel obejmuje również kategorie i limity wykorzystania związane z siecią, w tym Fast Data Transfer i Fast Origin Transfer. (vercel.com) Przy naszym profilu ruchu Cloudflare był znacznie korzystniejszy ekonomicznie.
Oszczędności nie wynikały wyłącznie ze zmiany dostawcy. Wiele aplikacji przenieśliśmy też na statyczne buildy Vite, ograniczyliśmy niepotrzebne SSR, uczyniliśmy buforowanie bardziej jawnym i przenieśliśmy API do lepiej dopasowanego środowiska uruchomieniowego. Ceny Cloudflare, szczególnie w zakresie transferu danych, miały jednak duży udział w zaobserwowanej przez nas obniżce kosztów o około 80%.
Ta liczba dotyczy konkretnie naszych obciążeń. Nie twierdzimy, że każda aplikacja przenoszona z Vercel do Cloudflare zaoszczędzi 80%.
API trafiły ostatecznie na Railway
API stanowiły osobny problem.
Choć były projektami Next.js, w praktyce były już od niego w dużej mierze niezależne. API WebCatalog liczyło około 34 000 linii kodu, ale tylko siedem plików importowało next/server. tRPC korzystał już z adaptera Fetch, a Inngest obsługiwał Hono.
Zastąpiliśmy warstwę HTTP Next.js przez Hono. Ten etap okazał się zaskakująco prosty.
Trudniejszym pytaniem było, gdzie uruchomić API. Zwykłe Workers nie pasowały dobrze, ponieważ API korzystają z Node.js i natywnych zależności, takich jak sharp.
Najpierw wypróbowaliśmy Cloudflare Containers, co pozwoliło nam szybko odejść z Vercel. Po uruchomieniu tego rozwiązania na produkcji okazało się jednak, że jego model operacyjny wymagał więcej ręcznego dostrajania zasobów, niż wtedy chcieliśmy.
Przenieśliśmy więc API ponownie.
Dziś działają na Railway jako zwykłe, stale działające usługi kontenerowe Node.js. CI (ciągła integracja) buduje obrazy Docker i przesyła je do naszego rejestru, a Railway obsługuje automatyczne skalowanie poziome.
Nie wszystko musi działać na brzegu sieci.
Odejście z Vercel oznaczało przejęcie większej odpowiedzialności
Niższy koszt wiązał się z kompromisem.
Vercel i Next.js obsługiwały wiele aspektów infrastruktury na wyższym poziomie abstrakcji. W TanStack Start i Workers postawiliśmy zamiast tego na jawne buforowanie HTTP. Strona jest renderowana, my zwracamy nagłówki sterujące buforowaniem, a Cloudflare zapisuje odpowiedź w pamięci podręcznej. Buforowanie w przeglądarce i na brzegu sieci może podlegać różnym zasadom, w tym odświeżaniu nieaktualnej zawartości w tle na brzegu sieci.
Podobało nam się, że te decyzje stały się jawne, ale jawne zarządzanie infrastrukturą oznacza również, że sami odpowiadamy za błędy.
Popełniliśmy kilka. Jedna produkcyjna reguła buforowania przypadkowo obejmowała odpowiedzi funkcji serwerowych TanStack przeznaczone dla konkretnych użytkowników, a inna konfiguracja buforowania źle współdziałała z mechanizmem przekierowywania ścieżek SPA do strony startowej. Te błędy przypomniały nam, że poprawności migracji nie da się dowieść, analizując wyłącznie repozytorium. Infrastruktura produkcyjna też jest częścią systemu.
Agenci AI zmienili sposób, w jaki przeprowadziliśmy migrację
Większość prac implementacyjnych wykonali agenci programistyczni, przede wszystkim Claude Fable 5 i Claude Opus 5.
Migracje między frameworkami szczególnie dobrze nadają się dla agentów, ponieważ duża część pracy ma jasny punkt odniesienia w istniejącym rozwiązaniu. Przenieś tę ścieżkę, zachowaj ten URL, zastąp to API frameworka, zachowaj te same metadane, uruchom sprawdzanie typów, popraw błędy i porównaj wynik ze starą implementacją.
Dla webcatalog.io prowadziliśmy rejestr postępów migracji, dzieląc pracę na etapy, takie jak przygotowanie podstaw, strony produktów, strony katalogowe, wyszukiwanie, blog, cennik, przekierowania, mapy witryny i przełączenie na nowe rozwiązanie. Zamiast prosić agenta: „Przenieś webcatalog.io na TanStack Start”, dawaliśmy mu ograniczone zadania z jasno określonymi niezmiennymi wymaganiami.
Istniejąca aplikacja stała się specyfikacją. Zadaniem agenta było odtworzenie jej zachowania w nowej architekturze, a nie wymyślanie produktu na nowo.
Dzięki temu wysiłek ludzi przesunął się z ręcznego przepisywania kodu na pytania takie jak: Czy ta strona naprawdę powinna używać SSR? Które zachowanie jest zamierzone? Co można buforować globalnie? Które adresy URL muszą pozostać identyczne? Gdzie powinno odbywać się uwierzytelnianie? Kiedy należy przestać próbować dopasować dane podejście?
Nadal przeglądaliśmy wyniki, uruchamialiśmy buildy i sprawdzanie typów oraz ręcznie weryfikowaliśmy obszary podwyższonego ryzyka, takie jak uwierzytelnianie, SEO (optymalizacja pod kątem wyszukiwarek), przekierowania i buforowanie.
Istotną zmianą nie było to, że AI pisała za nas kod. Istotne było to, że dzięki AI eksperymentowanie stało się tańsze.
Wypróbowanie Astro i usunięcie go po pięciu dniach było znacznie łatwiejsze do uzasadnienia. Pierwsze przeniesienie API, a potem uznanie, że inna platforma pasuje lepiej, było mniej bolesne. Mechaniczna część pracy kosztowała mniej, więc mogliśmy zmienić kierunek, gdy architektura okazywała się niewłaściwa, zamiast kontynuować tylko dlatego, że już zbyt wiele w nią zainwestowaliśmy.
Dzięki agentom prace implementacyjne przestały być tak ograniczonym zasobem.
To zwiększyło znaczenie architektury, ograniczeń, przeglądu i weryfikacji.
Z 80% zrobiło się około 90%
Po migracji koszty naszej infrastruktury webowej były niższe o około 80%.
Potem zaczęliśmy uważniej przyglądać się temu, które żądania w ogóle docierały do aplikacji.
Znaczna część publicznego ruchu internetowego jest generowana automatycznie: przez wyszukiwarki, roboty indeksujące dla AI, agentów, narzędzia pobierające dane ze stron, skanery i mniej przyjazne boty. Część tego ruchu jest przydatna, a część nie.
Ponieważ aplikacje znajdowały się bezpośrednio za Cloudflare, zaostrzyliśmy reguły bezpieczeństwa, aby szkodliwy ruch automatyczny był odrzucany na brzegu sieci, zanim spowoduje obciążenie aplikacji lub bazy danych.
Dzięki temu nasze łączne oszczędności sięgnęły około 90% w porównaniu ze starym rozwiązaniem.
Na końcowy wynik złożyło się kilka czynników: niższe ceny Cloudflare, szczególnie za transfer danych; mniej pracy po stronie serwera; środowisko uruchomieniowe lepiej dopasowane do naszych API; bardziej jawne buforowanie; oraz mniejsza liczba niepotrzebnych żądań docierających do aplikacji.
Gdzie jesteśmy teraz
W naszym repozytorium nie ma już aplikacji Next.js. Większość aplikacji webowych to obecnie SPA oparte na Vite + TanStack Router i działające na Cloudflare Workers. webcatalog.io i lexibird.com używają TanStack Start na Cloudflare Workers, ponieważ te witryny rzeczywiście korzystają z zalet SSR. Nasze API używają Hono + tRPC na Railway i działają jako zwykłe kontenery Node.js. Niemal wszystkie nasze projekty frontendowe należą teraz do ekosystemu Vite.
Koszty infrastruktury spadły o około 80%, do czego w dużym stopniu przyczyniły się znacznie korzystniejsze dla naszego ruchu ceny Cloudflare, zwłaszcza za transfer danych. Filtrowanie szkodliwego ruchu automatycznego na brzegu sieci podniosło łączne oszczędności do około 90%.
Najważniejszy jest jednak dla nas rezultat architektoniczny. Strony marketingowe korzystają z SSR, wszystko, co może być SPA, jest SPA, a API działają na zwykłych serwerach tam, gdzie to prostsze.
Zaczęliśmy od próby obniżenia kosztów Vercel.
Skończyliśmy, uświadamiając sobie, że nie potrzebowaliśmy większości architektury, za którą płaciliśmy.