
Per anni, Vercel è stata la piattaforma su cui distribuivamo quasi tutto ciò che pubblicavamo sul web in WebCatalog. La maggior parte di quei progetti usava Next.js, quindi l'abbinamento era naturale. I nostri siti di marketing, le interfacce dei prodotti, le impostazioni degli account, la console per sviluppatori, l'autenticazione, gli strumenti di amministrazione e le API finivano tutti su uno stack più o meno uguale.
Per molto tempo ha funzionato bene. Vercel semplificava i deployment, Next.js ci offriva un framework full-stack produttivo e raramente dovevamo pensare all'infrastruttura sottostante.
Col tempo, però, quella comodità ha smesso di corrispondere alle esigenze dei nostri prodotti.
I nostri siti di marketing traevano ancora vantaggio dall'SSR (rendering lato server), ma la maggior parte delle altre applicazioni web no. Potevano essere semplici SPA (applicazioni a pagina singola). Le nostre API avevano bisogno di un normale ambiente Node.js, con dipendenze native e processi di lunga durata. Quasi tutto il resto del nostro stack frontend usava già Vite. Nel frattempo, i costi dell'infrastruttura diventavano sempre più difficili da ignorare, soprattutto con la crescita del traffico pubblico e automatizzato.
All'inizio abbiamo cercato di ottimizzare quello che avevamo già. Abbiamo valutato di mantenere Next.js e spostarlo su Cloudflare Workers tramite un adapter. Abbiamo provato Astro per i siti di marketing. Per un breve periodo abbiamo eseguito le nostre API su Cloudflare Containers.
Alla fine siamo approdati a una soluzione più semplice: la maggior parte delle nostre applicazioni web usa ora Vite + TanStack Router su Cloudflare Workers, i siti di marketing usano TanStack Start con SSR su Cloudflare Workers e le API usano Hono + tRPC su Railway, in container Node.js di lunga durata.
La migrazione ha ridotto i costi della nostra infrastruttura web di circa l'80%. Dopo aver anche rafforzato le regole di sicurezza di Cloudflare e bloccato più traffico automatizzato ostile all'edge, il risparmio complessivo è salito a circa il 90%.
Gran parte dell'implementazione della migrazione è stata svolta da agenti di programmazione AI, principalmente Claude Fable 5 e Claude Opus 5. Gli ingegneri hanno deciso l'architettura, definito i vincoli, esaminato le modifiche e verificato il risultato.
Non siamo partiti con l'idea di lasciare Vercel
Il nostro primo istinto è stato ottimizzare la configurazione esistente.
webcatalog.io era il punto di partenza più ovvio: riceve molto traffico pubblico, mentre la maggior parte delle sue pagine cambia relativamente di rado. Abbiamo scoperto che una parte eccessiva del sito veniva ancora renderizzata dinamicamente. Abbiamo quindi corretto il rendering dinamico involontario, aggiunto l'ISR (Incremental Static Regeneration) alle route ad alto traffico, reso più pagine memorizzabili nella cache e ottimizzato la costosa generazione delle sitemap.
Il lavoro è stato utile, ma ha anche messo più chiaramente in luce il problema di fondo. Dedicavamo sempre più tempo a capire perché contenuti relativamente statici risultassero dinamici, quale comportamento del framework lo avesse causato e quale funzionalità del framework dovessimo introdurre per renderli di nuovo memorizzabili nella cache.
Nel frattempo, molte delle nostre altre applicazioni avevano il problema opposto. Le impostazioni degli account, la console per sviluppatori, le applicazioni di amministrazione e la maggior parte delle interfacce dei prodotti non avevano alcun bisogno di rendering lato server. Erano applicazioni eseguite nel browser che comunicavano con delle API.
A quel punto la domanda ha smesso di essere «Come possiamo ridurre la fattura di Vercel?» ed è diventata «Cosa costruiremmo se queste applicazioni non fossero già applicazioni Next.js?».
Abbiamo valutato Next.js su Cloudflare
L'opzione meno dirompente era mantenere Next.js e spostarlo da Vercel a Cloudflare Workers.
L'abbiamo presa seriamente in considerazione perché Next.js può essere eseguito su Cloudflare tramite livelli adapter, che ci avrebbero permesso di conservare gran parte della struttura delle applicazioni esistenti.
Ma non consideravamo Next.js su Cloudflare equivalente a Next.js su Vercel. Next.js è strettamente integrato con il runtime, il sistema di deployment, il comportamento della cache e le funzionalità della piattaforma Vercel. Su Cloudflare, un adapter deve tradurre questi presupposti per un runtime diverso.
Può funzionare bene, ma se il nostro obiettivo era semplificare lo stack, aggiungere un ulteriore livello di compatibilità non ci sembrava l'ideale.
C'era anche una questione più ampia legata agli strumenti. Quasi tutto il resto che sviluppavamo per il frontend usava già Vite: applicazioni desktop, estensioni del browser, SPA e altri progetti lato client.
Next.js si era orientato fortemente verso Turbopack, che è migliorato moltissimo. Non era una critica alle prestazioni. Per noi, la differenza più importante riguardava l'ecosistema. Vite era già la base comune di gran parte del nostro codice frontend, con un ecosistema di plugin più ampio e configurazioni più riutilizzabili tra progetti.
Mantenere Next.js su Cloudflare avrebbe quindi risolto parte del problema dell'hosting, ma avrebbe conservato sia la disomogeneità dei framework sia un ecosistema separato per gli strumenti di build.
Abbiamo quindi fatto un passo indietro e valutato le esigenze reali di ciascun tipo di carico di lavoro.
Abbiamo suddiviso lo stack in base al carico di lavoro
Una volta smesso di cercare un sostituto universale di Next.js, l'architettura è diventata molto più semplice.
I nostri siti di marketing avevano bisogno dell'SSR perché includono pagine pubbliche localizzate, metadati, URL canonici e sitemap, e ricevono traffico dai motori di ricerca.
La maggior parte delle altre applicazioni web non aveva bisogno dell'SSR e poteva essere costituita semplicemente da applicazioni Vite con TanStack Router.
Le nostre API non avevano alcun bisogno di un framework React ed erano più adatte a un normale runtime Node.js.
La suddivisione finale era questa:
Applicazioni web
Vite + TanStack Router
│
└── Cloudflare Workers
Siti di marketing
TanStack Start + SSR
│
└── Cloudflare Workers
API
Hono + tRPC
│
└── Railway / container Node.js
La regola è diventata semplice: usare l'SSR dove offre un valore reale, una semplice SPA dove non lo offre e un runtime backend per i servizi backend.
La maggior parte delle applicazioni web è diventata una SPA
Le migrazioni delle SPA sono state la parte più facile.
La migrazione dell'applicazione web WebCatalog è stata particolarmente rapida: abbiamo creato lo scheletro della soluzione sostitutiva con Vite + TanStack Router, trasferito l'interfaccia, spostato il deployment su Cloudflare Workers ed eliminato la vecchia versione Next.js nell'arco della stessa mattinata.
Abbiamo ripetuto lo stesso processo per l'applicazione web Lexibird, le impostazioni degli account, la console per sviluppatori, l'autenticazione e le applicazioni di amministrazione. Dalla creazione dello scheletro della prima SPA all'eliminazione dell'ultima applicazione Next.js sono passati circa 19 giorni.
Per queste applicazioni, Cloudflare Workers è volutamente poco complesso. Vite genera gli asset statici, Cloudflare li serve e le route dell'applicazione ripiegano su index.html. Non c'è un runtime SSR, perché non c'è nulla che richieda rendering lato server.
La parte più difficile è stata preservare i comportamenti accumulatisi intorno a quelle applicazioni nel tempo. Alcune logiche di autenticazione hanno dovuto lasciare le route server di Next.js e passare all'API; inoltre, le versioni meno recenti dell'applicazione desktop WebCatalog dipendevano ancora da endpoint legacy che dovevamo mantenere funzionanti.
Spostare l'interfaccia React è stato facile.
Preservare i contratti è stato più difficile.
Abbiamo provato Astro e lo abbiamo eliminato cinque giorni dopo
I siti di marketing erano più difficili.
La nostra prima scelta è stata Astro, che sembrava una soluzione naturale per webcatalog.io e lexibird.com: entrambi sono siti pubblici ricchi di contenuti, e Astro si integra bene con Cloudflare.
Abbiamo iniziato a trasferire entrambi i siti e, cinque giorni dopo, ci siamo fermati.
Non c'era un unico difetto fatale. Si accumulavano invece piccole incompatibilità. Alcune route avevano bisogno di accedere al database durante il prerendering, mentre il nostro ambiente di build, per scelta, non disponeva delle credenziali del database di produzione. Le island React rendevano meno naturale la condivisione dello stato dell'applicazione. Abbiamo anche incontrato differenze tra le route prerenderizzate e quelle renderizzate a runtime, oltre ad alcuni attriti con gli strumenti del nostro repository.
Nessuno di questi problemi era impossibile da risolvere. Era proprio questo a rendere pericoloso proseguire: avremmo potuto facilmente passare altre settimane a sistemare un problema dopo l'altro.
Invece abbiamo eliminato l'implementazione e ricominciato.
Gli agenti AI hanno cambiato l'economia di quella decisione. Gran parte del lavoro meccanico di migrazione non aveva richiesto settimane di lavoro agli ingegneri, quindi il costo già sostenuto era inferiore ed era più facile ammettere che preferivamo un'altra architettura.
TanStack Start era più adatto
Abbiamo ricominciato la migrazione dei siti di marketing dal codice Next.js originale, questa volta usando TanStack Start.
Si adattava molto più naturalmente alle nostre esigenze, perché usavamo già TanStack Router nelle SPA: il routing, i loader, i parametri di ricerca e la navigazione seguivano quindi concetti simili. Il codice restava normale React e TypeScript, e il sistema di build era Vite.
Abbiamo migrato lexibird.com a TanStack Start in un giorno. Poi è stata la volta di webcatalog.io.
Il vantaggio non era che TanStack Start risolvesse magicamente ogni problema. Era che si inseriva nella direzione che il resto del nostro stack aveva già preso.
In un monorepo, questo conta. Strumenti di build condivisi significano più plugin e configurazioni riutilizzabili, meno casi particolari durante il debugging, meno cambi di contesto per gli sviluppatori e meno convenzioni specifiche di progetto che gli agenti di programmazione devono scoprire.
Cloudflare era molto più economico per il nostro traffico
L'architettura era solo una delle ragioni per cui la nostra fattura è diminuita. I prezzi di Cloudflare sono stati un fattore importante, soprattutto per la larghezza di banda.
Attualmente il piano Workers Paid di Cloudflare addebita principalmente le richieste e l'utilizzo della CPU e non aggiunge costi per il trasferimento dati o la larghezza di banda dei Workers. (developers.cloudflare.com) Per i siti web pubblici questo conta molto: una richiesta può consumare pochissima CPU e trasferire comunque HTML, JavaScript, immagini, font e altri asset.
Il modello tariffario di Vercel include anche categorie e quote relative all'uso della rete, tra cui Fast Data Transfer e Fast Origin Transfer. (vercel.com) Per il nostro profilo di traffico, i costi di Cloudflare erano nettamente più vantaggiosi.
Il risparmio non è derivato soltanto dal cambio di provider. Abbiamo anche convertito molte applicazioni in build statiche Vite, ridotto l'SSR non necessario, reso più esplicita la gestione della cache e spostato le API su un runtime più adatto. Ma i prezzi di Cloudflare, in particolare per la larghezza di banda, hanno contribuito in modo significativo alla riduzione di circa l'80% che abbiamo osservato.
Quel numero riguarda il nostro specifico carico di lavoro. Non significa che ogni applicazione che passa da Vercel a Cloudflare risparmierà l'80%.
Le API sono finite su Railway
Le API erano un problema a sé.
Pur essendo progetti Next.js, erano già in gran parte indipendenti da Next.js. L'API di WebCatalog contava circa 34.000 righe di codice, ma solo sette file importavano next/server. tRPC usava già il proprio adapter Fetch e Inngest supportava Hono.
Abbiamo sostituito il livello HTTP di Next.js con Hono. Questa parte è stata sorprendentemente semplice.
La domanda più difficile era dove eseguire le API. I Workers standard non erano adatti, perché le API usano Node.js e dipendenze native come sharp.
All'inizio abbiamo provato Cloudflare Containers, che ci ha permesso di lasciare rapidamente Vercel. Dopo aver utilizzato quella configurazione in produzione, però, abbiamo scoperto che il suo modello operativo richiedeva più regolazioni manuali della capacità di quante ne volessimo gestire in quel momento.
Così abbiamo spostato di nuovo le API.
Oggi vengono eseguite su Railway come normali servizi container Node.js di lunga durata. La CI (integrazione continua) crea le immagini Docker e le invia al nostro registry; Railway gestisce lo scaling orizzontale automatico.
Non tutto deve essere eseguito all'edge.
Lasciare Vercel significava assumersi più responsabilità
Il costo inferiore comportava un compromesso.
Vercel e Next.js avevano gestito molti aspetti dell'infrastruttura a un livello più alto. Con TanStack Start e Workers siamo passati invece a una gestione esplicita della cache HTTP. Una pagina viene renderizzata, noi restituiamo gli header di cache e Cloudflare memorizza la risposta. La cache del browser e quella all'edge possono seguire politiche diverse, compreso il comportamento stale-while-revalidate all'edge.
Ci piaceva rendere esplicite queste decisioni, ma un'infrastruttura gestita esplicitamente significa anche che gli errori sono responsabilità tua.
Ne abbiamo commessi alcuni. Una regola di cache in produzione ha accidentalmente incluso risposte di funzioni server di TanStack specifiche per il visitatore; un'altra configurazione della cache ha interagito male con il fallback delle SPA. Questi bug ci hanno ricordato utilmente che la correttezza di una migrazione non può essere dimostrata interamente esaminando il repository. Anche l'infrastruttura di produzione fa parte del sistema.
Gli agenti AI hanno cambiato il nostro modo di migrare
Gran parte del lavoro di implementazione è stata svolta da agenti di programmazione, principalmente Claude Fable 5 e Claude Opus 5.
Le migrazioni di framework si prestano particolarmente bene agli agenti, perché per buona parte del lavoro esiste già un riferimento chiaro. Sposta questa route, preserva questo URL, sostituisci questa API del framework, mantieni gli stessi metadati, esegui il controllo dei tipi, correggi gli errori e confronta il risultato con la vecchia implementazione.
Per webcatalog.io abbiamo mantenuto un registro della migrazione che suddivideva il lavoro in fasi: fondamenta, pagine dei prodotti, pagine del catalogo, ricerca, blog, prezzi, redirect, sitemap e passaggio alla nuova versione. Invece di chiedere a un agente di «migrare webcatalog.io a TanStack Start», gli abbiamo assegnato compiti circoscritti con invarianti esplicite.
L'applicazione esistente è diventata la specifica. Il compito dell'agente era riprodurne il comportamento con la nuova architettura, non reinventare il prodotto.
Questo ha spostato il lavoro umano dalla traduzione manuale del codice a domande come: questa pagina dovrebbe davvero usare l'SSR? Quale comportamento è intenzionale? Che cosa può essere memorizzato in una cache globale? Quali URL devono rimanere identici? Dove deve avvenire l'autenticazione? Quando dobbiamo smettere di cercare di far funzionare un approccio?
Abbiamo comunque esaminato il risultato, eseguito build e controlli dei tipi e verificato manualmente le aree più rischiose, come l'autenticazione, la SEO (ottimizzazione per i motori di ricerca), i redirect e la cache.
Il cambiamento importante non è stato che l'AI scrivesse codice per noi. È stato che l'AI ha reso meno costosa la sperimentazione.
Provare Astro ed eliminarlo dopo cinque giorni è diventato molto più facile da giustificare. Spostare le API una volta e poi decidere che un'altra piattaforma era più adatta è stato meno doloroso. Il lavoro meccanico costava meno, quindi potevamo cambiare direzione quando l'architettura era sbagliata, anziché andare avanti solo perché vi avevamo già investito troppo.
Gli agenti hanno reso meno scarsa la capacità di implementazione.
Questo ha reso più importanti l'architettura, i vincoli, la revisione e la verifica.
L'80% è diventato circa il 90%
Dopo la migrazione, i costi della nostra infrastruttura web erano inferiori di circa l'80%.
Poi abbiamo iniziato a prestare più attenzione alle richieste che raggiungevano le applicazioni.
Una quota significativa del traffico Internet pubblico è automatizzata: motori di ricerca, crawler AI, agenti, scraper, scanner e bot meno amichevoli. Una parte di quel traffico è utile, un'altra no.
Con le applicazioni direttamente dietro Cloudflare, abbiamo rafforzato le regole di sicurezza in modo da respingere il traffico automatizzato ostile all'edge, prima che attivasse elaborazioni dell'applicazione o interrogazioni al database.
Dopo questo intervento, il risparmio complessivo ha raggiunto circa il 90% rispetto alla vecchia configurazione.
La riduzione finale è dipesa dalla combinazione di diversi fattori: i prezzi più bassi di Cloudflare, soprattutto per la larghezza di banda; meno elaborazioni lato server; un runtime più adatto alle nostre API; una gestione più esplicita della cache; e meno richieste inutili che raggiungono le applicazioni.
La soluzione a cui siamo arrivati
Nel nostro repository non sono rimaste applicazioni Next.js. La maggior parte delle nostre applicazioni web è costituita ora da SPA Vite + TanStack Router su Cloudflare Workers. webcatalog.io e lexibird.com usano TanStack Start su Cloudflare Workers perché questi siti traggono un reale vantaggio dall'SSR. Le nostre API usano Hono + tRPC su Railway, eseguiti in normali container Node.js. Quasi tutti i nostri progetti frontend fanno ora parte dell'ecosistema Vite.
I costi della nostra infrastruttura sono diminuiti di circa l'80%, grazie in larga misura ai costi nettamente più vantaggiosi di Cloudflare per il nostro traffico, soprattutto per la larghezza di banda. Filtrare il traffico automatizzato ostile all'edge ha portato il risparmio complessivo a circa il 90%.
Ma il risultato che ci sta più a cuore è quello architetturale. I siti di marketing hanno l'SSR, tutto ciò che può essere una SPA è una SPA e le API vengono eseguite su server veri quando questa è la soluzione più semplice.
Siamo partiti cercando di rendere Vercel meno costoso.
Alla fine abbiamo capito che gran parte dell'architettura per cui pagavamo non ci serviva.