本地开发时间减少 83%:为何迁出 Next.js

原文:Reducing local dev time by 83%: Why we migrated off Next.js

翻译:TUARAN

欢迎关注 {{前端周刊}},每周更新国外论坛的前端热门文章,紧跟时事,掌握前端技术动态。

Inngest 团队非常重视开发者体验(DX)。但当本地开发时页面首次加载要等 10--12 秒,"把体验做漂亮"就会变成一种消耗。

本文讲述他们为什么、以及如何从 Next.js 迁出,转向 TanStack Start,并在迁移后把本地首次加载时间大幅降到 2--3 秒(作者给出的量化结果是减少 83%)。

早期信号:我们努力让它能用,但它越来越慢

作者加入 Inngest 时,团队已经深度使用 Next.js:

当时的承诺很诱人:摆脱 SPA 的空白 loading 与瀑布式请求,获得嵌套布局与 streaming,并把技术栈收敛到一个框架。

但"蜜月期"很快结束。作者认为 Next.js 优化的是一种特定工作流:有专门前端团队、长期深耕框架细节。而对他们这种小团队(多数工程师要多线作战)来说,认知负担会不断累积:

  • "use client" / "use server" 的边界
  • 多层缓存 API
  • RSC 与客户端组件之间并不清晰的分界

这些都让非"全职前端"的工程师感觉是在和框架搏斗,而不是在交付功能。

先退一步:弱化 RSC

他们先尝试弱化 RSC:尽量只用最少量的 server components,并偏好 client components。短期内 DX 变得可接受了一些。

但随之而来的问题是:变慢------非常慢。

本地开发环境的首次页面加载时间推到至少 10--12 秒。

Slack 里不断出现抱怨:"我讨厌这个。""前端太慢了。"

最终大家达成一致:我们的开发者体验很糟。

升级 Next.js、上 Turbopack

他们尝试升级 Next.js,并使用 Vercel 的 profiling 工具评估效果,但没有改善。

接着试 Turbopack(还试了两次)。对一个规模不小的代码库来说,这不轻松:需要升级依赖与做不少重构。

更麻烦的是:当时 Vercel 生产环境构建仍主要支持 Webpack,导致本地开发与生产构建链路不一致,带来额外问题。

最终 Turbopack 对本地首屏加载的改善有限,平均也就快了几秒。

作者的结论很直白:Turbopack 并没有那么 turbo,是时候看看 Next.js 之外的选择。

评估替代方案

他们希望得到:

  • 更快的本地首次加载
  • 更合理的路由 API
  • 更清晰的 server/client 约定

于是原型验证了三种选择:

  • TanStack Start
  • Deno Fresh
  • React Router v7(本质上是 Remix 方向)

作者以前用过 Fresh 与 Remix,都认可它们的成熟度。但:

  • Fresh 从 1 到 2 的节奏让团队有点犹豫
  • Remix 与 React Router 的合并/拆分演进让他们有所顾虑
  • TanStack Start 当时还是 RC,但团队已经大量使用 TanStack 的其它产品,对其方向很乐观

综合权衡后,他们决定押注 TanStack Start。

迁移策略:增量还是一次性撕掉创可贴

他们需要在两种方式中选:

  • 一次性迁移(brute force):能更快收敛,但会带来巨大的 PR、很难传统评审。
  • 增量迁移(incremental):需要条件路由、共享组件库里大量 Next.js 工具的替换与兼容,基础设施工作更多。

为了估算成本,作者先从 Dev Server(dashboard 路由的一个子集)开始试转。结果转得比预期快,于是直接一路做到底:

  • Dev Server 约一周完成切换
  • Dashboard 路由更多更复杂,整体多花了一些时间
  • 总体仍是"一个工程师 + AI 辅助"在几周内完成

迁移过程中,共享组件凡是依赖 Next.js 的地方,就复制一份改成 TanStack 等价实现;遇到 app heads 之间的交叉引用,也用一些临时类型 hack 过渡。

结果:DX 大幅改善

迁移后,他们的本地开发体验显著变好:

  • 首次加载通常不超过 2--3 秒
  • 且几乎只发生在"第一次加载任一路由"
  • 之后的路由切换/加载几乎都是即时

作者强调:这与 Next.js 形成鲜明对比------在他们的体验里,Next.js 本地开发时"每个路由的首次加载"都容易很慢。

技术取舍:从约定驱动到显式配置

他们认为核心差异在于:

  • Next.js 更偏 convention-over-configuration,但有时"魔法且模糊"
  • TanStack Start 更偏显式配置 + 规定式的 loader 数据获取

作者用两个片段对比了 Next.js App Router 与 TanStack Router 的风格差异。

Next.js App Router(示意)

tsx 复制代码
export default async function RootLayout({
  params: { environmentSlug },
  children,
}: RootLayoutProps) {
  const env = await getEnv(environmentSlug);

  return (
    <>
      <Layout activeEnv={env}>
        <Env env={env}>
          <SharedContextProvider>{children}</SharedContextProvider>
        </Env>
      </Layout>
    </>
  );
}

布局与服务端数据获取"混在一起",唯一提示它在服务端:async/await

TanStack Router(示意)

tsx 复制代码
export const Route = createFileRoute('/_authed/env/$envSlug')({
  component: EnvLayout,
  notFoundComponent: NotFound,
  loader: async ({ params }) => {
    const env = await getEnvironment({
      data: { environmentSlug: params.envSlug },
    });

    if (params.envSlug && !env) {
      throw notFound({ data: { error: 'Environment not found' } });
    }

    return { env };
  },
});

function EnvLayout() {
  const { env } = Route.useLoaderData();

  return (
    <>
      <EnvironmentProvider env={env}>
        <SharedContextProvider>
          <Outlet />
        </SharedContextProvider>
      </EnvironmentProvider>
    </>
  );
}

作者解释:这里的 getEnvironmentcreateServerFn,只会在 server 执行;useLoaderData 则在 client 侧读取路由数据。

AI 在迁移中怎么用

他们对 AI 的定位很务实:主要让 AI 做"体力活",而不是做架构决策。

做法大致是:

  • 先人工迁移一条路线并建立模式(server/client 数据获取、组织方式)
  • 再让 AI 把模式复制到相似路由
  • 人工复查与清理

此外,AI 也用来辅助处理一些 TypeScript 与边角 bug。

经验与建议

TanStack Start 相关

  • 尽早、频繁 build:一旦有较多 server-side 代码,迟早会遇到"错误地被打进 client/server"的 bundling 问题;构建间隔越小,越容易定位。
  • 不要只依赖 dev mode:他们遇到过 dev 与 build 后行为不同的情况;有疑问就本地 build + preview。

迁移过程相关

  • 一次性迁移意味着巨大 PR:几乎无法传统方式评审;他们选择用充分的 UAT 来对冲风险。
  • 他们确实遇到过一次需要立即回滚的问题(某个难以在生产外测试的集成流程)。
  • 如果你的工程环境非常风险厌恶,可能更值得投入额外工程实现"增量切换"。

如何做你自己的决策

作者把迁移结果开源在 UI monorepo:

github.com/inngest/inn...

相关推荐
arvin_xiaoting1 小时前
OpenClaw学习总结_I_核心架构_8:SessionPruning详解
前端·chrome·学习·系统架构·ai agent·openclaw·sessionpruning
工程师老罗2 小时前
Image(图像)的用法
java·前端·javascript
swipe3 小时前
把 JavaScript 原型讲透:从 `[[Prototype]]`、`prototype` 到 `constructor` 的完整心智模型
前端·javascript·面试
问道飞鱼4 小时前
【前端知识】React 组件生命周期:从底层原理到实践场景
前端·react.js·前端框架·生命周期
CHU7290354 小时前
定制专属美丽时刻:美容预约商城小程序的贴心设计
前端·小程序
浩~~4 小时前
反射型XSS注入
前端·xss
AwesomeDevin5 小时前
AI时代,我们的任务不应沉溺于与 AI 聊天,🤔 从“对话式编程”迈向“数字软件工厂”
前端·后端·架构
harrain5 小时前
antvG2折线图和区间range标记同时绘制
前端·javascript·vue.js·antv·g2
德育处主任Pro5 小时前
从重复搭建到高效生产,RollCode的H5开发新范式
前端
蜡台5 小时前
SPA(Single Page Application) Web 应用(即单页应用)架构模式 更新
前端·架构·vue·react·spa·spa更新