
Протягом багатьох років Vercel був платформою, на якій ми за замовчуванням розгортали майже все, що працювало в інтернеті у WebCatalog. Більшість цих проєктів використовували Next.js, тож таке поєднання було природним. Наші маркетингові сайти, продуктові інтерфейси, налаштування облікових записів, консоль розробника, автентифікація, адміністративні інструменти й API — усе опинилося приблизно на одному технологічному стеку.
Довгий час це добре працювало. Vercel спрощував розгортання, Next.js давав нам продуктивний повнофункціональний фреймворк, і нам рідко доводилося замислюватися про інфраструктуру, що стояла за ними.
Зрештою ця зручність перестала відповідати потребам наших продуктів.
Нашим маркетинговим сайтам і далі був корисний SSR (рендеринг на боці сервера), але більшості інших вебзастосунків він не був потрібен. Вони могли бути звичайними SPA (односторінковими застосунками). Для наших API було потрібне звичайне середовище Node.js із нативними залежностями та процесами, що працюють тривалий час. Майже все інше у нашому фронтенд-стеку вже використовувало Vite. Водночас ігнорувати витрати на інфраструктуру ставало дедалі важче, особливо зі зростанням публічного й автоматизованого трафіку.
Спочатку ми спробували оптимізувати те, що вже мали. Ми розглядали можливість залишити Next.js і перенести його на Cloudflare Workers через адаптер. Спробували Astro для маркетингових сайтів. Ненадовго розмістили наші API у Cloudflare Containers.
Зрештою ми прийшли до простішого рішення: більшість наших вебзастосунків тепер використовують Vite + TanStack Router на Cloudflare Workers, маркетингові сайти — TanStack Start із SSR на Cloudflare Workers, а API — Hono + tRPC на Railway у вигляді контейнерів Node.js, що працюють постійно.
Міграція зменшила наші витрати на вебінфраструктуру приблизно на 80%. Коли ми також посилили правила безпеки Cloudflare й почали блокувати більше ворожого автоматизованого трафіку на периферії мережі, загальна економія зросла приблизно до 90%.
Більшу частину роботи з реалізації міграції також виконали ШІ-агенти для написання коду, переважно Claude Fable 5 і Claude Opus 5. Інженери визначали архітектуру й обмеження, перевіряли зміни та результат.
Ми не починали з відмови від Vercel
Першим нашим інстинктом було оптимізувати наявну конфігурацію.
Найочевиднішим місцем для початку був webcatalog.io: він отримує багато публічного трафіку, хоча більшість його сторінок змінюються відносно рідко. Ми виявили, що надто велика частина сайту все ще рендерилася динамічно. Тому ми усунули ненавмисний динамічний рендеринг, додали ISR (інкрементальну статичну регенерацію) для маршрутів із великим трафіком, зробили більше сторінок придатними до кешування й оптимізували ресурсомістку роботу з картами сайту.
Ця робота допомогла, але й чіткіше показала основну проблему. Ми витрачали дедалі більше часу на з’ясування того, чому відносно статичний вміст є динамічним, яка поведінка фреймворку це спричинила та який механізм фреймворку потрібно застосувати, щоб знову зробити його придатним до кешування.
Водночас багато наших інших застосунків мали протилежну ситуацію. Налаштування облікових записів, консоль розробника, адміністративні застосунки й більшість продуктових інтерфейсів узагалі не потребували серверного рендерингу. Це були браузерні застосунки, які взаємодіяли з API.
Тоді питання змінилося з «Як зменшити наш рахунок за Vercel?» на «Що б ми створили, якби ці застосунки вже не були застосунками Next.js?»
Ми розглядали Next.js на Cloudflare
Найменш радикальним варіантом було залишити Next.js і перенести його з Vercel на Cloudflare Workers.
Ми серйозно розглядали цей варіант, адже Next.js може працювати на Cloudflare через адаптери, що дозволило б нам зберегти значну частину наявної структури застосунків.
Але ми не вважали Next.js на Cloudflare еквівалентом Next.js на Vercel. Next.js найтісніше інтегрований із середовищем виконання Vercel, його системою розгортання, механізмами кешування й можливостями платформи. На Cloudflare адаптер має перенести ці припущення на інше середовище виконання.
Це може добре працювати, але якщо наша мета — спростити стек, додавати ще один шар сумісності не здавалося ідеальним рішенням.
Існувало й ширше питання інструментів. Майже все інше, що ми розробляли для фронтенду, уже використовувало Vite: настільні застосунки, розширення браузера, SPA та інші клієнтські проєкти.
Next.js значною мірою перейшов на Turbopack, і Turbopack суттєво покращився. Ми не нарікали на продуктивність. Для нас важливішою відмінністю була екосистема. Vite вже був спільною основою для більшої частини нашого фронтенд-коду, із ширшою екосистемою плагінів і конфігурацією, яку легше повторно використовувати в різних проєктах.
Отже, збереження Next.js на Cloudflare розв’язало б частину проблеми з хостингом, але залишило б і невідповідність фреймворків, і окрему екосистему інструментів збирання.
Тому ми зробили крок назад і подивилися, що насправді потрібно для кожного типу завдань.
Ми розділили стек за типами завдань
Щойно ми перестали шукати одну універсальну заміну Next.js, архітектура стала набагато простішою.
Маркетинговим сайтам був потрібен SSR, оскільки вони мають загальнодоступні локалізовані сторінки, метадані, канонічні URL-адреси, карти сайту й трафік із пошукових систем.
Більшості інших наших вебзастосунків SSR не був потрібен: вони могли бути просто застосунками Vite з TanStack Router.
Нашим API узагалі не був потрібен React-фреймворк — їм краще підходило звичайне середовище Node.js.
У підсумку розподіл став таким:
Вебзастосунки
Vite + TanStack Router
│
└── Cloudflare Workers
Маркетингові сайти
TanStack Start + SSR
│
└── Cloudflare Workers
API
Hono + tRPC
│
└── Railway / контейнери Node.js
Правило стало простим: використовувати SSR там, де він дає реальну користь, звичайний SPA — там, де SSR не потрібен, а серверні служби запускати у відповідному серверному середовищі.
Більшість вебзастосунків стали SPA
Міграція на SPA була найлегшою частиною.
Вебзастосунок WebCatalog ми перенесли особливо швидко: створили основу заміни на Vite + TanStack Router, перенесли інтерфейс, переключили розгортання на Cloudflare Workers і видалили стару версію на Next.js — усе за один ранок.
Ми повторили той самий підхід для вебзастосунку Lexibird, налаштувань облікових записів, консолі розробника, автентифікації та адміністративних застосунків. Від створення основи першого SPA до видалення останнього застосунку Next.js минуло близько 19 днів.
Для цих застосунків Cloudflare Workers навмисно використовується максимально просто. Vite збирає статичні ресурси, Cloudflare їх віддає, а маршрути застосунку за потреби повертаються до index.html. Середовища SSR немає, бо нічого не потребує серверного рендерингу.
Складніше було зберегти поведінку, що накопичилася навколо цих застосунків із часом. Частину логіки автентифікації довелося перенести із серверних маршрутів Next.js до API, а старі версії настільного застосунку WebCatalog усе ще залежали від застарілих кінцевих точок, працездатність яких ми мали зберегти.
Перенести інтерфейс React було легко.
Зберегти контракти — складніше.
Ми спробували Astro й видалили його через п’ять днів
З маркетинговими сайтами було складніше.
Спочатку ми обрали Astro. Він здавався природним рішенням для webcatalog.io та lexibird.com, адже обидва сайти загальнодоступні, містять багато контенту, а Astro добре інтегрується з Cloudflare.
Ми почали переносити обидва сайти, а через п’ять днів зупинилися.
Не було якоїсь однієї критичної вади. Натомість накопичувалися дрібні невідповідності. Деяким маршрутам під час попереднього рендерингу був потрібен доступ до бази даних, тоді як наше середовище збирання навмисно не мало облікових даних для доступу до робочої бази. React-острівці ускладнювали використання спільного стану застосунку. Ми також зіткнулися з відмінностями між попередньо зрендереними маршрутами й маршрутами, що рендеряться під час виконання, а також із певними труднощами з інструментами в нашому репозиторії.
Жодна з цих проблем не була нерозв’язною. Саме тому продовжувати було небезпечно: ми легко могли витратити ще кілька тижнів, виправляючи одне за одним.
Натомість ми видалили реалізацію й почали знову.
ШІ-агенти змінили економіку цього рішення. Значна частина механічної роботи з перенесення не забрала тижнів інженерного часу, тож уже вкладені ресурси були меншими і нам було легше визнати, що ми віддаємо перевагу іншій архітектурі.
TanStack Start підійшов краще
Ми почали міграцію маркетингових сайтів наново, взявши за основу початковий код на Next.js, але цього разу використали TanStack Start.
Він підійшов значно природніше: ми вже використовували TanStack Router у SPA, тому маршрутизація, завантажувачі даних, параметри пошуку й навігація спиралися на подібні принципи. Код залишався звичайним React і TypeScript, а системою збирання був Vite.
Ми перенесли lexibird.com на TanStack Start за день. Потім настала черга webcatalog.io.
Перевага полягала не в тому, що TanStack Start чарівним чином розв’язав усі проблеми. Він просто відповідав напрямку, у якому вже рухався решта нашого стеку.
У монорепозиторії це має значення. Спільні інструменти збирання означають більше плагінів і конфігурацій, які можна використовувати повторно, менше особливих випадків під час налагодження, менше перемикання контексту для розробників і менше специфічних для проєктів домовленостей, які доводиться з’ясовувати агентам для написання коду.
Cloudflare був набагато дешевшим для нашого трафіку
Архітектура була лише частиною причини зменшення рахунку. Важливу роль відіграла тарифна модель Cloudflare, особливо щодо пропускної здатності.
Зараз у платному тарифі Cloudflare Workers плата стягується переважно за запити й використання процесорного часу, а окрема плата за передавання даних або пропускну здатність для Workers не нараховується. (developers.cloudflare.com) Для публічних сайтів це дуже важливо: запит може потребувати зовсім мало процесорного часу, але водночас передавати HTML, JavaScript, зображення, шрифти та інші ресурси.
Тарифна модель Vercel також передбачає категорії й ліміти, пов’язані з мережевим використанням, зокрема Fast Data Transfer і Fast Origin Transfer. (vercel.com) Для нашого профілю трафіку економіка Cloudflare була значно вигіднішою.
Економія виникла не лише завдяки зміні провайдера. Ми також перевели багато застосунків на статичні збірки Vite, скоротили непотрібний SSR, чіткіше визначили кешування та перенесли API в середовище виконання, яке їм краще підходить. Але тарифи Cloudflare, особливо щодо пропускної здатності, суттєво вплинули на зафіксоване нами зниження витрат приблизно на 80%.
Ця цифра стосується саме нашого навантаження. Ми не стверджуємо, що кожен застосунок після переходу з Vercel на Cloudflare заощадить 80%.
API опинилися на Railway
API були окремим завданням.
Попри те, що це були проєкти Next.js, вони вже здебільшого не залежали від нього. API WebCatalog налічував приблизно 34 000 рядків коду, але лише сім файлів імпортували next/server. tRPC уже використовував адаптер Fetch, а Inngest підтримував Hono.
Ми замінили HTTP-оболонку Next.js на Hono. Ця частина виявилася напрочуд невеликою.
Складнішим було питання, де це запускати. Звичайні Workers не підходили, оскільки API використовують Node.js і нативні залежності, як-от sharp.
Спочатку ми спробували Cloudflare Containers, що дозволило нам швидко піти з Vercel. Але після запуску цієї конфігурації в робочому середовищі ми виявили, що її експлуатаційна модель вимагала більше ручного налаштування потужностей, ніж нам тоді хотілося.
Тому ми знову перенесли API.
Зараз вони працюють на Railway як звичайні контейнерні служби Node.js, що працюють тривалий час. CI (безперервна інтеграція) збирає Docker-образи й надсилає їх до нашого реєстру, а Railway забезпечує горизонтальне автомасштабування.
Не все має працювати на периферії мережі.
Відмова від Vercel означала більше відповідальності
Зниження витрат мало свою ціну.
Vercel і Next.js забезпечували багато інфраструктурних механізмів на вищому рівні. На TanStack Start і Workers ми натомість перейшли до явно заданого HTTP-кешування. Сторінка рендериться, ми повертаємо заголовки керування кешем, а Cloudflare кешує відповідь. Політики кешування у браузері та на периферії мережі можуть відрізнятися, зокрема щодо використання застарілого вмісту під час його повторної перевірки на периферії.
Нам подобалося, що ці рішення стали явними, але це також означає, що відповідальність за помилки лежить на нас.
І ми припустилися кількох. Одне правило кешування у робочому середовищі випадково охопило персоналізовані відповіді серверних функцій TanStack, а інша конфігурація кешу невдало взаємодіяла з поведінкою резервного маршруту SPA. Ці помилки нагадали нам, що правильність міграції неможливо довести, спираючись лише на репозиторій. Робоча інфраструктура — теж частина системи.
ШІ-агенти змінили наш підхід до міграції
Більшу частину роботи з реалізації виконали агенти для написання коду, переважно Claude Fable 5 і Claude Opus 5.
Міграції між фреймворками особливо добре підходять агентам, адже для значної частини роботи існує чіткий зразок. Перенести цей маршрут, зберегти цю URL-адресу, замінити цей API фреймворку, залишити ті самі метадані, запустити перевірку типів, виправити помилки й порівняти результат зі старою реалізацією.
Для webcatalog.io ми вели трекер міграції, який ділив роботу на етапи: основа, сторінки продуктів, сторінки каталогу, пошук, блог, тарифи, перенаправлення, карти сайту й переключення на нову версію. Замість прохання до агента «перенести webcatalog.io на TanStack Start» ми давали йому обмежені за обсягом завдання з чітко визначеними незмінними вимогами.
Наявний застосунок став специфікацією. Завданням агента було відтворити його поведінку на новій архітектурі, а не переосмислити продукт.
Це змістило людські зусилля з ручного переписування коду на такі питання: чи справді цій сторінці потрібен SSR? Яка поведінка є навмисною? Що можна кешувати глобально? Які URL-адреси мають залишитися незмінними? Де має відбуватися автентифікація? Коли варто припинити спроби змусити певний підхід працювати?
Ми все одно перевіряли результат, запускали збирання й перевірку типів, а також вручну тестували ділянки з підвищеним ризиком, зокрема автентифікацію, SEO (пошукову оптимізацію), перенаправлення та кешування.
Важлива зміна полягала не в тому, що ШІ писав за нас код. ШІ здешевив експерименти.
Спробувати Astro й видалити його через п’ять днів стало набагато легше виправдати. Перенести API, а потім вирішити, що інша платформа підходить краще, було менш болісно. Механічна робота коштувала дешевше, тож ми могли змінювати напрямок, коли архітектура виявлялася невдалою, замість продовжувати лише тому, що вже забагато в неї вклали.
Завдяки агентам реалізація перестала бути таким дефіцитним ресурсом.
Це зробило архітектуру, визначення обмежень, перевірку змін і результату ще важливішими.
80% перетворилися приблизно на 90%
Після міграції наші витрати на вебінфраструктуру були приблизно на 80% нижчими.
Потім ми почали уважніше стежити за тим, які запити взагалі доходять до застосунків.
Значну частину публічного інтернет-трафіку створюють автоматизовані системи: пошукові системи, ШІ-краулери, агенти, скрейпери, сканери й менш дружні боти. Частина такого трафіку корисна, а частина — ні.
Оскільки застосунки тепер були безпосередньо за Cloudflare, ми посилили правила безпеки, щоб ворожий автоматизований трафік відхилявся на периферії мережі ще до того, як він спричинить обчислення в застосунку або роботу з базою даних.
Після цього загальна економія порівняно зі старою конфігурацією сягнула приблизно 90%.
Підсумкове скорочення витрат стало результатом поєднання кількох чинників: нижчих тарифів Cloudflare, особливо на передавання даних; меншого обсягу серверної роботи; середовища виконання, яке краще підходить нашим API; чіткіше визначеного кешування; а також того, що зайві запити взагалі перестали доходити до застосунків.
До чого ми прийшли
У нашому репозиторії більше немає застосунків Next.js. Більшість наших вебзастосунків тепер — це SPA на Vite + TanStack Router у Cloudflare Workers. webcatalog.io та lexibird.com використовують TanStack Start на Cloudflare Workers, оскільки цим сайтам SSR справді корисний. Наші API працюють на Hono + tRPC у Railway у вигляді звичайних контейнерів Node.js. Майже всі наші фронтенд-проєкти тепер належать до екосистеми Vite.
Наші витрати на інфраструктуру зменшилися приблизно на 80%, значною мірою завдяки набагато вигіднішій для нашого трафіку тарифній моделі Cloudflare, особливо щодо передавання даних. Фільтрація ворожого автоматизованого трафіку на периферії мережі збільшила загальну економію приблизно до 90%.
Але найбільше ми цінуємо архітектурний результат. Маркетингові сайти отримують SSR, усе, що може бути SPA, працює як SPA, а API запускаються на повноцінних серверах, коли так простіше.
Ми почали з намагання зробити Vercel дешевшим.
А закінчили усвідомленням, що більша частина архітектури, за яку ми платили, нам не потрібна.