当构建时间成为每次提交的隐性税负时,团队需要认真审视技术栈是否仍在为业务服务。Railway 团队在 2026 年初完成了一次生产级前端迁移:将运行在 Next.js Pages Router 上的整个前端应用迁移至 Vite + TanStack Router + Nitro 的技术组合。迁移完成后,构建时间从超过 10 分钟降至 2 分钟以内,开发服务器实现秒级启动,整个迁移过程通过两个 Pull Request 完成,且未产生任何停机时间。这篇文章将深入拆解 Railway 做出这一决策的技术原因、迁移策略的具体执行方式,以及我们可以从中提取的可落地参数。

构建瓶颈的量化诊断

Railway 团队在博客中给出的数据极为精确:前端构建总时长超过 10 分钟,其中仅 Next.js 本身就消耗了约 6 分钟,而在这 6 分钟中,有一半的时间卡在「finalizing page optimization」这一阶段。对于一个每天多次部署的团队而言,这意味着每次提交都需要等待超过 10 分钟才能验证构建结果,而 Next.js 的编译优化阶段成了迭代速度的刚性瓶颈。

这个问题的根源并非 Next.js 本身存在缺陷,而是 Railway 的产品特性与 Next.js 的设计初衷产生了错配。Railway 的核心产品是一个高度交互式的仪表板,Canvas 实时协作、WebSocket 遍布全站,本质上是一个客户端驱动的富状态应用。Next.js 的 Server-First 理念 —— 服务端渲染、静态生成、ISR—— 在这个场景下几乎无用武之地。团队甚至坦承,他们在 Pages Router 上构建了自己的抽象层来弥补布局和路由能力的不足,而这些能力在现代前端框架中本应是第一等公民。

更关键的是,Railway 当时仍在使用 Pages Router。App Router 虽然能解决部分布局问题,但它的设计更加倚重服务端渲染范式,对于一个客户端主导的产品来说,迁移到 App Router 意味着围绕一个他们根本不需要的范式重建整个应用。这种投入产出比的不合理,是推动团队最终决定离开 Next.js 的核心动因。

技术选型的决策框架

Railway 在评估新栈时明确了几个硬性需求:客户端优先的渲染模型、类型安全的路由系统、第一等的布局原语、以及足够快的开发循环。他们没有选择继续在 Next.js 生态内调优,而是将目光投向了更契合其开发模式的组合:Vite + TanStack Start + Nitro。

选择 Vite 的原因非常直接:毫秒级的开发服务器启动、即时的 HMR 反馈、基于 ESM 的构建模型在生产环境下同样高效。Vite 的模块拆分策略 —— 每个模块生成独立的内容哈希 chunk—— 意味着更新单个业务模块(如计费模块)只会使该模块的 chunk 失效,用户无需重新下载整个 bundle。对于 Railway 这种多模块的复杂 dashboard 而言,这一特性直接转化为更低的缓存失效率和更快的二次加载速度。

TanStack Start(也就是 TanStack Router 的全栈框架版本)被选中,则是因为它在几个关键维度上击中了 Railway 的痛点。首先是类型安全的路由:路由参数和查询参数在编译期被推导,整个路由树的自动补全在 IDE 中开箱即用,路由从文件系统直接生成。其次是原生的布局系统:Pathless Layout Routes 将之前在 Pages Router 中通过各种 Hack 实现的功能变为可组合、可预测的第一等原语。第三是显式模型:TanStack 不依赖框架「魔法」,团队对数据流和渲染逻辑拥有完全的控制权。Railway 团队中有多人在假期期间试用 TanStack Start,最终的反馈一致积极,这种开发体验的主观感受在技术选型中占了相当权重。

Nitro 在这一组合中承担服务层职责。Railway 用 Nitro 替换了原有的 next.config.js 配置,将 500+ 重定向、安全头和缓存规则统一管理。Nitro 还提供了 SSR 能力,但仅用于真正需要的场景 —— 营销页面、Changelog、招聘页面采用服务端渲染,除此之外的所有路由均为纯客户端渲染。这种按需使用 SSR 的策略,避免了对客户端驱动产品的过度设计。

两阶段 PR 的迁移策略

最值得深入分析的环节是 Railway 如何在不停机的情况下完成这一迁移。对于一个服务于数百万用户、拥有超过 200 条路由的生产级应用,传统做法通常是数月的并行运行和增量切换。Railway 的做法是将迁移拆解为两个完全解耦的 Pull Request。

第一阶段 PR 的核心目标是消除所有 Next.js 特定依赖,但保持框架本身不变。团队逐一替换了 next/imagenext/headnext/router 这三个核心 API:图像使用原生 <img> 标签配合 Fastly 边缘图像优化服务,元信息管理改为自研的小型库,路由操作切换到 TanStack Router 的 API。这个阶段只改变了依赖声明,没有触及任何业务逻辑和框架配置,其目的是为下一阶段的框架切换创造一个干净的接口。

第二阶段 PR 才是真正的框架迁移。团队首先系统性地将页面文件中所有与路由无关的逻辑提取为独立的 React 组件,然后从原始页面树直接生成 TanStack Router 的路由定义。这一步骤的技术细节值得注意:他们并非逐个页面手动迁移,而是通过脚本从既有页面结构批量生成路由配置,保证 200+ 条路由在迁移过程中不出现遗漏。Nitro 被添加为新的服务端层,替代 Next.js 的服务端能力,所有 Node.js API 的 Polyfill(如 Buffer、url.parse)被替换为浏览器原生实现。合并这个 PR 的时间点选在周日凌晨,团队在 Discord 上建立了实时 War Room,修复补丁在同一天内持续推送,最终实现了零停机切换。

这种分阶段策略的工程价值在于:它将迁移风险从「一次性大规模切换」分解为两个可控的增量变更。第一阶段实际上是回归测试的安全网 —— 如果替换了 next/image 等 API 后出现渲染异常,团队可以在不触及框架的情况下快速定位和修复。第二阶段由于已经消除了所有 Next.js 强耦合,框架切换本质上只是一个配置变更,失败的回滚成本也极低。

关键性能参数与可量化收益

Railway 博客中给出的性能数据具有直接的参考价值。以下是从原始信息中提取的关键指标:

构建时间从超过 10 分钟降至 2 分钟以内,意味着每次 CI/CD 管道的执行效率提升了至少 5 倍。细分来看,原有构建中 Next.js 消耗约 6 分钟,其中约 3 分钟卡在「finalizing page optimization」阶段。迁移后的构建时间中,Vite 的生产构建消耗约 1 分半钟,加上 Nitro 的服务端构建,总时长控制在 2 分钟以内。这个对比清晰地表明,移除 Next.js 的页面优化阶段是构建加速的最主要来源。

开发服务器启动时间从分钟级降至秒级。Vite 的即时启动能力使得团队在修改代码后几乎立刻能看到结果,HMR 的延迟可以忽略不计。Railway 团队在博客中特别提到「the feedback cycle effectively disappears」,这种体验层面的改善对高频迭代团队的士气有显著影响。

边缘缓存策略同样带来了显著的运行时收益。Fastly 承担了大部分流量的边缘服务,营销页面全量缓存,动态页面使用 ISR 按需失效。前端服务器在日常负载下几乎处于空闲状态。Vite 的内容哈希 chunk 策略使得单模块变更只会导致对应 chunk 的缓存失效,用户在后续访问中只需下载变更的模块(通常是几十 KB),而非重新获取整个 JS bundle。

权衡与取舍

任何技术迁移都不是单向收益,Railway 团队明确列出了他们在这一决策中放弃的东西。

首先是内置的图像优化能力。Next.js 的 next/image 组件提供了自动格式转换、尺寸适配、懒加载等开箱即用的功能。迁移后,Railway 使用原生 <img> 标签配合 Fastly 的边缘图像优化服务实现了同等能力,但这需要额外的边缘配置和 CDN 成本。团队将这部分工作评价为「Straightforward to build, no extra dependencies」,即实现难度不大,但需要自行维护。

其次是部分生态系统工具的替代成本。next-seonext-sitemap 这类在 Next.js 生态中成熟的库被替换为自研的小型内部实现。虽然团队认为实现难度不高,但这意味着后续的维护责任从开源社区转移到了内部团队。

第三是框架成熟度的风险。TanStack Start 作为一个相对较新的框架,确实存在一些粗糙的边缘情况。Railway 团队对此的态度是:「We're comfortable with that because the direction is right, the maintainers are responsive.」他们同时赞助了 Vite 和 TanStack 的开发,以实际行动支持项目的发展方向。这种态度在技术选型中值得参考:当一个新框架在核心方向上正确、且维护者响应积极时,早期采用的风险是可管理的。

可复用的工程决策清单

从 Railway 的案例中,我们可以提取若干可落地的工程决策参数,适用于正在评估前端框架迁移或构建优化的团队。

当构建时间中 Next.js 优化阶段占比超过 30% 且产品本质上为客户端驱动时,应该认真评估是否继续使用 Next.js。Server-First 框架对客户端驱动产品的隐性成本在于:团队在适配框架范式上投入的精力往往超过框架带来的实际收益。

构建优化最有效的手段是移除不必要的框架负担,而非在既有框架内调优。Railway 投入大量时间在 Next.js 配置层面进行缓存优化、依赖裁剪,但真正的收益来自直接移除这个框架。如果你的构建时间瓶颈同样集中在框架的静态分析或优化阶段,迁移到一个更轻量的方案可能比继续优化更有效。

两阶段迁移策略值得优先考虑。先消除所有框架特定依赖(API 调用、配置文件、构建工具链),再进行框架切换。这种方式的额外工作量约为 20%-30%,但它将迁移风险分解为两个独立的可回滚节点,大幅降低了生产环境出现问题的概率。

CI 环境中的 .next/cache 持久化仍然对 Next.js 项目有价值。如果团队决定暂不迁移而是继续使用 Next.js,根据 Next.js 官方文档的建议,在 CI 中持久化构建缓存可以将重复构建时间缩短 40%-60%。

框架迁移的决策应该将开发体验纳入考量。Railway 团队选择 TanStack Start 的理由中,「我们喜欢用它来构建」这一主观因素占了相当权重。对于高频迭代的产品,前端开发的主观效率 —— 启动速度、HMR 反馈、类型系统的完善程度 —— 最终会转化为客观的迭代速度和 bug 率。

Railway 的案例并非鼓吹所有团队都放弃 Next.js,但它提供了一个清晰的量化模板:当框架与产品的核心需求产生结构性错配时,及时评估并采取行动可以将迭代效率提升一个数量级。这种决策的成本核算方式 —— 用构建时间的具体分钟数、每日的部署次数、团队的开发体验来衡量 —— 比泛泛的「技术先进性」讨论更有参考价值。


参考资料

  • Railway Blog: Moving Railway's Frontend Off Next.js
  • Next.js Official: CI Build Caching Guide