How Moving From Vercel to Cloudflare Workers + Railway Cut Our Web Infrastructure Costs by 80%

We migrated our web stack from Vercel to Cloudflare Workers and Railway, replacing Next.js with a mix of TanStack Router, TanStack Start, and Hono based on what each workload actually needed. The result was roughly 80% lower infrastructure costs, rising to around 90% after filtering hostile automated traffic at the edge. Most of the migration work was carried out by AI coding agents, with engineers focused on architecture, constraints, review, and production verification.

September 23, 2026

Quang Lam · Founder & CEO

How Moving From Vercel to Cloudflare Workers + Railway Cut Our Web Infrastructure Costs by 80%

For years, Vercel was the default place we deployed almost everything on the web at WebCatalog. Most of those projects used Next.js, so the pairing was natural. Our marketing sites, product interfaces, account settings, developer console, authentication, admin tools, and APIs all ended up on roughly the same stack.

That worked well for a long time. Vercel made deployments simple, Next.js gave us a productive full-stack framework, and we rarely had to think about the infrastructure underneath either of them.

Eventually, that convenience stopped matching the shape of our products.

Our marketing sites still benefited from SSR (server-side rendering), but most of our other web apps did not. They could be plain SPAs (single-page applications). Our APIs wanted a normal Node.js environment with native dependencies and long-running processes. Almost everything else in our frontend stack was already using Vite. At the same time, infrastructure costs were becoming harder to ignore, particularly as public and automated traffic grew.

We initially tried to optimize what we already had. We considered keeping Next.js and moving it to Cloudflare Workers through an adapter. We tried Astro for the marketing sites. We briefly put our APIs on Cloudflare Containers.

Eventually we ended up somewhere simpler: most of our web apps now use Vite + TanStack Router on Cloudflare Workers, our marketing sites use TanStack Start with SSR on Cloudflare Workers, and our APIs use Hono + tRPC on Railway as long-running Node.js containers.

The migration reduced our web infrastructure costs by roughly 80%. After we also tightened our Cloudflare security rules and blocked more hostile automated traffic at the edge, the total savings increased to around 90%.

Most of the migration implementation was also carried out by AI coding agents, primarily Claude Fable 5 and Claude Opus 5, with engineers deciding the architecture, defining constraints, reviewing changes, and verifying the result.

We Didn't Start by Leaving Vercel

Our first instinct was to optimize the existing setup.

webcatalog.io was the most obvious place to start because it receives a lot of public traffic while most of its pages change relatively infrequently. We found that too much of the site was still being dynamically rendered, so we fixed accidental dynamic rendering, added ISR (Incremental Static Regeneration) to high-traffic routes, made more pages cacheable, and optimized expensive sitemap behavior.

The work helped, but it also made the underlying problem clearer. We were spending increasing amounts of time understanding why relatively static content was dynamic, which framework behavior had caused it, and which framework primitive we needed to introduce to make it cacheable again.

Meanwhile, many of our other applications had the opposite problem. Account settings, the developer console, admin applications, and most product interfaces did not need server rendering at all. They were browser applications talking to APIs.

At that point the question stopped being “How do we make our Vercel bill smaller?” and became “What would we build if these applications were not already Next.js applications?”

We Considered Next.js on Cloudflare

The least disruptive option was to keep Next.js and move it from Vercel to Cloudflare Workers.

We seriously considered this because Next.js can run on Cloudflare through adapter layers, which would have let us preserve much of the existing application structure.

But we did not view Next.js on Cloudflare as equivalent to Next.js on Vercel. Next.js is most tightly integrated with Vercel's runtime, deployment system, caching behavior, and platform features. On Cloudflare, an adapter has to translate those assumptions onto a different runtime.

That can work well, but if our goal was to simplify the stack, adding another compatibility layer did not feel ideal.

There was also a broader tooling issue. Almost everything else we were building on the frontend already used Vite: desktop applications, browser extensions, SPAs, and other client-side projects.

Next.js had moved heavily toward Turbopack, and Turbopack has improved greatly. This was not a performance complaint. For us, the bigger difference was ecosystem. Vite was already the common foundation across most of our frontend codebase, with a broader plugin ecosystem and more reusable configuration across projects.

Keeping Next.js on Cloudflare would therefore have solved part of the hosting problem, but it would have preserved both the framework mismatch and a separate build-tool ecosystem.

So we stepped back and looked at what each workload actually needed.

We Split the Stack by Workload

Once we stopped looking for one universal Next.js replacement, the architecture became much simpler.

Our marketing sites needed SSR because they have public, localized pages, metadata, canonical URLs, sitemaps, and search-engine traffic.

Most of our other web apps did not need SSR and could simply be Vite applications using TanStack Router.

Our APIs did not need a React framework at all and were better suited to a normal Node.js runtime.

The final split looked like this:

Web apps
Vite + TanStack Router
        │
        └── Cloudflare Workers

Marketing sites
TanStack Start + SSR
        │
        └── Cloudflare Workers

APIs
Hono + tRPC
        │
        └── Railway / Node.js containers

The rule became straightforward: use SSR where it provides real value, use a plain SPA where it doesn't, and run backend services in a backend runtime.

Most of the Web Apps Became SPAs

The SPA migrations were the easiest part.

The WebCatalog web app moved particularly quickly: we scaffolded the Vite + TanStack Router replacement, ported the interface, switched deployment to Cloudflare Workers, and removed the old Next.js version in the same morning.

We repeated the same pattern for the Lexibird web app, account settings, our developer console, authentication, and admin applications. From the first SPA scaffold to deleting the final Next.js application took about 19 days.

For these applications, Cloudflare Workers is intentionally boring. Vite builds static assets, Cloudflare serves them, and application routes fall back to index.html. There is no SSR runtime because there is nothing that needs server rendering.

The harder part was preserving the behavior that had accumulated around those applications over time. Some authentication logic had to move out of Next.js server routes and into the API, and older versions of the WebCatalog desktop app still depended on legacy endpoints that we had to keep working.

Moving the React UI was easy.

Preserving contracts was harder.

We Tried Astro and Deleted It Five Days Later

The marketing sites were harder.

Our first choice was Astro, which looked like a natural fit for webcatalog.io and lexibird.com because both are public, content-heavy sites and Astro integrates well with Cloudflare.

We started porting both sites, and five days later we stopped.

There was no single fatal flaw. Instead, small mismatches kept accumulating. Some routes needed database access during prerendering while our build environment intentionally did not have production database credentials. React islands made shared application state less natural. We also ran into differences between prerendered and runtime-rendered routes and some tooling friction in our repository.

None of those problems was impossible to solve. That was exactly why continuing was dangerous. We could easily have spent another few weeks fixing one thing after another.

Instead, we deleted the implementation and started again.

AI agents changed the economics of that decision. A large amount of the mechanical port had not consumed weeks of engineer time, so the sunk cost was smaller and it was easier to admit that we preferred another architecture.

TanStack Start Fit Better

We restarted the marketing migration from the original Next.js code, this time using TanStack Start.

It fit much more naturally because we were already using TanStack Router in the SPA applications, so routing, loaders, search parameters, and navigation followed similar concepts. The code remained ordinary React and TypeScript, and the build system was Vite.

We ported lexibird.com to TanStack Start in a day. webcatalog.io followed.

The advantage was not that TanStack Start magically solved every problem. It was that it fit where the rest of our stack was already going.

In a monorepo, that matters. Shared build tooling means more reusable plugins and configuration, fewer special cases when debugging, less context switching for developers, and fewer project-specific conventions for coding agents to discover.

Cloudflare Was Much Cheaper for Our Traffic

Architecture was only part of the reason our bill dropped. Cloudflare's pricing was a major factor, especially bandwidth.

Cloudflare Workers Paid currently charges primarily for requests and CPU usage and does not add data-transfer or bandwidth charges for Workers. (developers.cloudflare.com) That matters a lot for public websites because a request may use very little CPU while still transferring HTML, JavaScript, images, fonts, and other assets.

Vercel's pricing model also includes network-related usage categories and allowances, including Fast Data Transfer and Fast Origin Transfer. (vercel.com) For our traffic profile, Cloudflare's economics were significantly better.

The savings did not come only from changing providers. We also moved many applications to static Vite builds, reduced unnecessary SSR, made caching more explicit, and moved API workloads to a runtime that fit them better. But Cloudflare's pricing, particularly around bandwidth, was a major contributor to the roughly 80% reduction we observed.

That number is specific to our workload. It is not a claim that every application moving from Vercel to Cloudflare will save 80%.

The APIs Ended Up on Railway

The APIs were a separate problem.

Despite being Next.js projects, they were already mostly independent from Next.js. The WebCatalog API was roughly 34,000 lines, but only seven files imported next/server. tRPC was already using its Fetch adapter, and Inngest supported Hono.

We replaced the Next.js HTTP shell with Hono. That part was surprisingly small.

The harder question was where to run it. Plain Workers were not a good fit because the APIs use Node.js and native dependencies such as sharp.

We initially tried Cloudflare Containers, which got us off Vercel quickly. But after running that setup in production, we found that the operational model required more manual capacity tuning than we wanted at the time.

So we moved the APIs again.

Today they run on Railway as ordinary long-lived Node.js container services. CI (continuous integration) builds Docker images, pushes them to our registry, and Railway handles horizontal autoscaling.

Not everything needs to run at the edge.

Leaving Vercel Meant Owning More

The lower cost came with a tradeoff.

Vercel and Next.js had handled many infrastructure behaviors at a higher level. On TanStack Start and Workers, we moved toward explicit HTTP caching instead. A page is rendered, we return cache headers, and Cloudflare caches the response. Browser and edge caching can have different policies, including stale-while-revalidate behavior at the edge.

We liked having those decisions become explicit, but explicit infrastructure also means the mistakes are yours.

We made a few. One production cache rule accidentally matched visitor-specific TanStack server-function responses, and another cache configuration interacted badly with SPA fallback behavior. Those bugs were useful reminders that migration correctness cannot be proven entirely from the repository. Production infrastructure is part of the system too.

AI Agents Changed How We Migrated

Most of the implementation work was performed by coding agents, primarily Claude Fable 5 and Claude Opus 5.

Framework migrations are unusually well suited to agents because much of the work has a clear existing reference. Move this route, preserve this URL, replace this framework API, keep the same metadata, run typecheck, fix the errors, and compare the result with the old implementation.

For webcatalog.io, we maintained a migration tracker that divided the work into stages such as foundation, product pages, catalog pages, search, blog, pricing, redirects, sitemaps, and cutover. Instead of asking an agent to “migrate webcatalog.io to TanStack Start,” we gave it bounded tasks with explicit invariants.

The existing application became the specification. The agent's job was to reproduce behavior using the new architecture, not reinvent the product.

That shifted human effort away from manually translating code and toward questions like: Should this page actually use SSR? Which behavior is intentional? What may be cached globally? Which URLs must remain identical? Where should authentication happen? When should we stop trying to make an approach work?

We still reviewed the output, ran builds and typechecks, and manually verified higher-risk areas such as authentication, SEO (search engine optimization), redirects, and caching.

The important change was not that AI wrote code for us. It was that AI made experimentation cheaper.

Trying Astro and deleting it after five days became much easier to justify. Moving the APIs once and then deciding another platform was a better fit was less painful. The mechanical work was cheaper, so we could change direction when the architecture was wrong instead of continuing because we had already invested too much in it.

Agents made implementation less scarce.

That made architecture, constraints, review, and verification more important.

80% Became Roughly 90%

After the migration, our web infrastructure cost was roughly 80% lower.

Then we started paying more attention to which requests were reaching the applications at all.

A meaningful amount of public internet traffic is automated: search engines, AI crawlers, agents, scrapers, scanners, and less friendly bots. Some of that traffic is useful and some is not.

With the applications directly behind Cloudflare, we tightened our security rules so hostile automated traffic could be rejected at the edge before it triggered application compute or database work.

After that, our total savings reached roughly 90% compared with the old setup.

The final reduction came from several things working together: Cloudflare's lower pricing, especially around bandwidth; less server-side work; a better runtime for our APIs; more explicit caching; and fewer unnecessary requests reaching the applications at all.

Where We Ended Up

There are no Next.js applications left in our repository. Most of our web apps are now Vite + TanStack Router SPAs on Cloudflare Workers. webcatalog.io and lexibird.com use TanStack Start on Cloudflare Workers because those sites genuinely benefit from SSR. Our APIs use Hono + tRPC on Railway, running as ordinary Node.js containers. Almost all of our frontend projects now live in the Vite ecosystem.

Our infrastructure cost dropped by roughly 80%, with Cloudflare's significantly better economics for our traffic, particularly bandwidth, contributing heavily. Filtering hostile automated traffic at the edge pushed the overall savings to around 90%.

But the result we care about most is architectural. Marketing sites get SSR, everything else that can be a SPA is a SPA, and APIs run on real servers when real servers are simpler.

We started by trying to make Vercel cheaper.

We ended by realizing we did not need most of the architecture we were paying for.