从 Vercel 迁移到 Cloudflare Workers + Railway,如何让我们的 Web 基础设施成本降低了 80%

我们将 Web 技术栈从 Vercel 迁移到了 Cloudflare Workers 和 Railway,并根据各项工作负载的实际需求,用 TanStack Router、TanStack Start 和 Hono 的组合替代了 Next.js。迁移后,基础设施成本降低了约 80%;在边缘节点过滤恶意自动化流量后,降幅进一步扩大至约 90%。大部分迁移工作由 AI 编码代理完成,工程师则主要负责架构设计、约束设定、代码审查和生产环境验证。

2026年9月23日

Quang Lam · Founder & CEO

从 Vercel 迁移到 Cloudflare Workers + Railway,如何让我们的 Web 基础设施成本降低了 80%

多年来,Vercel 一直是 WebCatalog 几乎所有 Web 项目的默认部署平台。大多数项目都使用 Next.js,因此这样的组合顺理成章。我们的营销网站、产品界面、账户设置、开发者控制台、身份认证、管理工具和 API,最终都采用了大致相同的技术栈。

这套方案在很长一段时间里都运作良好。Vercel 让部署变得简单,Next.js 提供了高效的全栈框架,我们也很少需要操心底层基础设施。

但最终,这种便利不再适合我们产品的实际需求。

营销网站仍然能从 **SSR(服务端渲染)**中受益,但其他大多数 Web 应用并不需要 SSR,完全可以做成普通的 SPA(单页应用)。我们的 API 则需要一个支持原生依赖和长期运行进程的常规 Node.js 环境。与此同时,前端技术栈中几乎所有其他项目都已经在使用 Vite。基础设施成本也越来越难以忽视,尤其是在公开访问流量和自动化流量增长之后。

起初,我们尝试优化现有方案。我们考虑过保留 Next.js,通过适配器将其迁移到 Cloudflare Workers;也尝试过用 Astro 构建营销网站,还短暂地将 API 部署到了 Cloudflare Containers。

最终,我们选择了更简单的方案:现在,大多数 Web 应用采用 Vite + TanStack Router,部署在 Cloudflare Workers 上;营销网站采用 TanStack Start,在 Cloudflare Workers 上进行 SSR;API 则采用 Hono + tRPC,以长期运行的 Node.js 容器形式部署在 Railway 上。

这次迁移让我们的 Web 基础设施成本降低了约 80%。后来,我们又收紧了 Cloudflare 安全规则,在边缘拦截更多恶意自动化流量,总体节省幅度提高到了约 90%。

迁移中的大部分实现工作也由 AI 编程代理完成,主要使用 Claude Fable 5 和 Claude Opus 5。工程师负责确定架构、定义约束、审查改动并验证结果。

我们一开始并没打算离开 Vercel

我们的第一反应是优化现有部署方案。

webcatalog.io 是最明显的起点:它承载大量公开访问流量,但大多数页面的内容变化相对较少。我们发现网站中仍有太多页面在动态渲染,于是修复了意外触发的动态渲染,为高流量路由添加了 ISR(增量静态再生),让更多页面可以缓存,并优化了开销较高的站点地图相关处理。

这些工作确实有所帮助,但也让底层问题更加清晰:我们花了越来越多的时间弄清楚为什么相对静态的内容会被动态渲染、是什么框架行为导致了这一点,以及需要引入哪种框架机制才能重新让它被缓存。

与此同时,许多其他应用面临的情况恰好相反。账户设置、开发者控制台、管理应用以及大多数产品界面根本不需要服务端渲染。它们只是调用 API 的浏览器应用。

于是,问题不再是“怎样降低 Vercel 账单?”,而变成了“如果这些应用原本不是 Next.js 应用,我们会怎样构建它们?”

我们考虑过在 Cloudflare 上运行 Next.js

改动最小的方案是保留 Next.js,只把部署平台从 Vercel 换成 Cloudflare Workers。

我们认真考虑过这个方案,因为借助适配层,Next.js 可以在 Cloudflare 上运行,现有应用结构也能大体保留。

但我们不认为,在 Cloudflare 上运行 Next.js 与在 Vercel 上运行 Next.js 是一回事。Next.js 与 Vercel 的运行时、部署系统、缓存行为和平台功能集成得最为紧密。在 Cloudflare 上,适配器必须把这些预设转换到另一个运行时上。

这种方式可以运作得很好,但如果目标是简化技术栈,再增加一层兼容层就不太理想。

工具链还有一个更普遍的问题:我们在前端构建的几乎所有其他项目都已经使用 Vite,包括桌面应用、浏览器扩展、SPA 和其他客户端项目。

Next.js 已经大力转向 Turbopack,而 Turbopack 也取得了长足进步。我们并不是在抱怨它的性能。对我们而言,更大的差别在于生态:Vite 已经是大部分前端代码库的共同基础,拥有更广泛的插件生态,项目之间也能复用更多配置。

因此,在 Cloudflare 上保留 Next.js 只能解决部分托管问题,却会继续保留框架上的不一致,以及一套独立的构建工具生态。

于是我们退一步,重新审视每类工作负载真正需要什么。

我们按工作负载拆分技术栈

当我们不再寻找一个能全面替代 Next.js 的方案后,架构反而简单了许多。

营销网站有公开、支持多语言的页面,还涉及元数据、规范网址、站点地图和搜索引擎流量,因此需要 SSR。

其他大多数 Web 应用不需要 SSR,使用 TanStack Router 构建 Vite 应用即可。

API 则完全不需要 React 框架,更适合运行在常规的 Node.js 环境中。

最终拆分如下:

Web 应用
Vite + TanStack Router
        │
        └── Cloudflare Workers

营销网站
TanStack Start + SSR
        │
        └── Cloudflare Workers

API
Hono + tRPC
        │
        └── Railway / Node.js 容器

原则变得很明确:SSR 只用在确实能带来价值的地方;不需要 SSR 时就用普通 SPA;后端服务则运行在适合后端的运行环境中。

大多数 Web 应用变成了 SPA

迁移 SPA 是最容易的部分。

WebCatalog Web 应用迁移得尤其快:我们搭建了 Vite + TanStack Router 替代项目的骨架,移植界面,将部署切换到 Cloudflare Workers,并在同一个上午删除了旧版 Next.js 应用。

随后,我们用同样的方式迁移了 Lexibird Web 应用、账户设置、开发者控制台、身份认证和管理应用。从搭建第一个 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 islands 让共享应用状态变得不那么自然。预渲染路由与运行时渲染路由之间存在差异,仓库里的工具链也带来了一些摩擦。

这些问题并非无解。恰恰因为如此,继续做下去才危险:我们很可能再花几个星期,不断地逐个解决问题。

于是,我们删除了这套实现,重新开始。

AI 代理改变了这个决定的成本结构。大量机械性的移植工作没有耗费工程师数周时间,沉没成本更低,我们也就更容易承认自己更倾向于另一种架构。

TanStack Start 更契合我们的需求

我们回到原有的 Next.js 代码,重新开始迁移营销网站,这次使用 TanStack Start。

它融入现有技术栈要自然得多:SPA 应用已经在使用 TanStack Router,所以路由、加载器、查询参数和导航都遵循类似的概念。代码仍然是普通的 React 和 TypeScript,构建系统则是 Vite。

我们只用一天就将 lexibird.com 迁移到了 TanStack Start,随后又迁移了 webcatalog.io。

它的优势并不是能神奇地解决所有问题,而是契合我们整个技术栈的发展方向。

在 monorepo 中,这一点很重要。共享的构建工具意味着插件和配置可以更多地复用;调试时需要处理的特例更少;开发者切换项目时需要转换的思路更少;编程代理需要摸索的项目专属约定也更少。

对我们的流量而言,Cloudflare 便宜得多

架构只是账单下降的部分原因。Cloudflare 的定价是一个重要因素,尤其是带宽方面。

Cloudflare Workers 付费套餐目前主要按请求量和 CPU 使用量收费,不会额外收取 Workers 的数据传输费或带宽费。(developers.cloudflare.com) 这对公开网站尤其重要,因为一次请求可能只消耗很少的 CPU,却仍要传输 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 项目,但实际上已经基本不依赖 Next.js。WebCatalog API 约有 34,000 行代码,却只有七个文件导入了 next/server。tRPC 已经在使用 Fetch 适配器,而 Inngest 也支持 Hono。

我们用 Hono 替换了 Next.js 的 HTTP 层。这部分改动出乎意料地小。

更难的问题是把它部署在哪里。普通 Workers 并不合适,因为这些 API 使用了 Node.js,以及 sharp 等原生依赖。

起初,我们尝试了 Cloudflare Containers,很快就把 API 从 Vercel 迁了出来。但在生产环境运行一段时间后,我们发现这种运维模式需要比当时预期更多的手动容量调优。

于是,我们又迁移了一次 API。

如今,它们作为常规的长期运行 Node.js 容器服务部署在 Railway 上。**CI(持续集成)**构建 Docker 镜像并推送到我们的镜像仓库,Railway 则负责水平自动扩缩容。

并非所有东西都需要运行在边缘。

离开 Vercel 意味着要自己承担更多责任

成本降低是有取舍的。

此前,Vercel 和 Next.js 在更高层级上替我们处理了许多基础设施行为。迁移到 TanStack Start 和 Workers 后,我们转而采用更明确的 HTTP 缓存策略:渲染页面、返回缓存响应头,再由 Cloudflare 缓存响应。浏览器缓存和边缘缓存可以采用不同策略,包括在边缘使用 stale-while-revalidate 行为。

我们喜欢这些决定变得明确,但基础设施越明确,出错的责任也越在自己身上。

我们确实犯过几次错。一条生产环境缓存规则意外匹配了与特定访客有关的 TanStack 服务端函数响应;另一项缓存配置则与 SPA 回退行为产生了不良交互。这些问题提醒我们:仅靠检查代码仓库,无法完全证明迁移的正确性。生产环境基础设施同样是系统的一部分。

AI 代理改变了我们的迁移方式

大部分实现工作由编程代理完成,主要使用 Claude Fable 5 和 Claude Opus 5。

框架迁移特别适合交给代理,因为大量工作都有清晰的现有实现可供参照:迁移这个路由、保留这个 URL、替换这个框架 API、保持相同的元数据、运行类型检查、修复错误,并与旧实现对比。

对于 webcatalog.io,我们维护了一份迁移跟踪清单,将工作划分为基础搭建、产品页面、目录页面、搜索、博客、定价、重定向、站点地图和正式切换等阶段。我们没有笼统地要求代理“把 webcatalog.io 迁移到 TanStack Start”,而是交给它范围明确、约束条件清晰的任务。

现有应用就是规格说明。代理的任务是用新架构复现现有行为,而不是重新设计产品。

这让人的精力从手动转换代码,转向思考这些问题:这个页面真的需要 SSR 吗?哪些行为是刻意设计的?哪些内容可以全局缓存?哪些 URL 必须保持不变?身份认证应该在哪里完成?什么时候应该停止尝试让某种方案可行?

我们仍然审查了产出,运行构建和类型检查,并对身份认证、SEO(搜索引擎优化)、重定向和缓存等高风险部分进行了手动验证。

重要的变化不是 AI 替我们写了代码,而是 AI 降低了试验成本。

试用 Astro 五天后删除它,变得更容易接受。先迁移一次 API,随后又认定另一个平台更合适,也没那么令人痛苦。机械性工作的成本降低了,所以当架构方向不对时,我们可以改变方向,而不必因为已经投入太多就硬着头皮继续。

代理让实现工作不再那么稀缺。

这也让架构设计、约束定义、审查和验证变得更加重要。

节省 80%,后来接近 90%

迁移完成后,我们的 Web 基础设施成本降低了约 80%。

接着,我们开始更仔细地关注:究竟有哪些请求真正到达了应用。

公共互联网上有相当一部分流量来自自动化程序:搜索引擎、AI 爬虫、代理、内容抓取程序、扫描器,以及不那么友善的机器人。其中有些流量有用,有些则没有。

应用直接部署在 Cloudflare 后,我们收紧了安全规则,让恶意自动化流量在边缘就被拦截,避免其触发应用计算或数据库操作。

此后,与旧方案相比,我们的总体节省幅度达到了约 90%。

最终的成本降幅来自多方面的共同作用:Cloudflare 更低的价格,尤其是带宽方面;更少的服务端工作;更适合 API 的运行时;更明确的缓存策略;以及从一开始就减少到达应用的不必要请求。

最终的架构

我们的代码仓库里已经没有 Next.js 应用了。现在,大多数 Web 应用都是部署在 Cloudflare Workers 上的 Vite + TanStack Router SPA。webcatalog.io 和 lexibird.com 则使用部署在 Cloudflare Workers 上的 TanStack Start,因为这两个网站确实能从 SSR 中受益。我们的 API 使用 Hono + tRPC,部署在 Railway 上,以常规 Node.js 容器形式运行。几乎所有前端项目如今都处于 Vite 生态中。

我们的基础设施成本降低了约 80%。Cloudflare 对我们的流量结构,尤其是带宽,提供了明显更有利的成本条件,是其中的重要原因。在边缘过滤恶意自动化流量后,总体节省幅度进一步提高到约 90%。

但我们最看重的结果是架构上的改变:营销网站使用 SSR;其他能做成 SPA 的应用就做成 SPA;如果用常规服务器运行 API 更简单,就让 API 运行在常规服务器上。

起初,我们只是想降低 Vercel 的费用。

最后,我们意识到:我们原本为之付费的架构,有很大一部分其实并不需要。