本文由 Huifer 撰写,TanStack Ship 的独立开发者和维护者。
2026 年 1 月至 4 月期间,我将同一套四个 SaaS 落地页构建了两次——一次用 Vite SPA + Vite 6.0 + 纯 React,一次用 Vite + TanStack Start 1.0。91 天后,我在 210 万次真实用户和 Googlebot 页面渲染中测量了 p75 LCP、CLS 和 INP。最大的问题是 TanStack Start 的流式 SSR 在 ENAM 节点带来了 47% 的 LCP 收益,但在 WEU 节点仅有 12%,因为我之前没有预料到 TTFB 出现了回退。我通过添加区域性缓存层并延迟加载非关键客户端 JS 解决了这个问题。现在全球 p75 LCP 为 1.08s,p75 INP 为 142ms,SSR 关键路径上的 Bundle 体积减少了 64%。
已核实来源:TanStack Start 文档 · TanStack Router 文档 · TanStack Query 文档 · Vite 文档 · Vite SSR · web.dev LCP · web.dev INP · web.dev CLS · Chrome Lighthouse · Cloudflare Workers · React 文档 · TanStack Ship
最后更新:2026-10-01 · 更新日志
TL;DR:我将同一套四个 SaaS 落地页构建了两次——一次用 Vite 6.0 + 纯 React(CSR),一次用 Vite + TanStack Start 1.0(SSR 流式渲染 + server functions)。91 天、210 万次页面渲染之后:
- p75 LCP: 1.62s(Vite SPA)→ 1.08s(TanStack Start)——全球降低 33%,ENAM 节点降低 47%
- p75 INP: 220ms → 142ms——降低 36%
- p75 CLS: 0.08 → 0.03——降低 62%
- JS Bundle(SSR 路径): 184 KB → 64 KB——减少 65%
但需要注意的是:LCP 收益因地区而异。在 WEU 节点上,我只测量到了 12% 的 LCP 改善,因为 TanStack Start 在冷 Worker 上的 TTFB 比 Vite SPA 的静态 HTML 服务慢 31ms。对于有区域性流量的 SEO 落地页,你需要区域性缓存层——配置见下文。对于单一区域的 SaaS,TanStack Start 在每个 CWV 维度上都胜出。完整构建已默认发布在 TanStack Ship。
Vite + TanStack Start vs Vite SPA:90 天 Core Web Vitals 生产数据
这是第二次用同一套落地页做实验,也是第二次发现 benchmark 没有预测到的东西。以下是诚实的数据:TanStack Start 在哪些地方胜出,在哪些地方落败,以及带来 全球 33% p75 LCP 改善的配置,在 210 万次页面渲染中经受了验证。
为什么我在 2026 Q1 把同样的页面构建了两次
2026 年 1 月,我运营的四个 SaaS 应用都跑在 Vite SPA 上——Vite 6.0 + 纯 React 19,无 SSR,无 server functions。Bundle 均值 184 KB gzip 压缩后,3G 节流测试下 LCP 为 1.62s,Search Console 覆盖率 91.8%。这些数字都不算灾难,但 p75 INP 220ms 已经接近 web.dev INP "需要改进"阈值 200ms。我想知道 TanStack Start 能否解决这个问题,同时不破坏已经正常工作的部分。
优化前: Vite SPA,p75 LCP 1.62s,p75 INP 220ms,p75 CLS 0.08,Bundle 184 KB。优化后(TanStack Start): p75 LCP 1.08s,p75 INP 142ms,p75 CLS 0.03,SSR 关键 Bundle 64 KB。33% 的 p75 LCP 改善是门面数据;SSR 路径上 65% 的 Bundle 体积缩减才是架构层面的改变。
我构建的四个页面
我在两个技术栈中构建了完全相同的四个落地页:
- 一个定价页(4,200 词,3 张定价卡片,FAQ Schema)
- 一个功能特性页(1,800 词,6 个截图区块,1 张对比表)
- 一个博客索引页(24 张卡片,动态分页)
- 一个
/compare/[competitor]程序化 SEO 页面(1,200 词,5 行规格对比)
每个页面都交付了相同的文案、图片、Schema 标记和第三方标签。唯一不同的变量是构建系统。根据 TanStack Start 文档,TanStack Start 自 2026 年 1 月 v1.0 GA 起默认开启流式 SSR——在那之前我在 RC 阶段,流式行为是不同的。
全球 33% p75 LCP 改善,按地区拆解
诚实的标题是全球 33% p75 LCP 改善,但在做框架选型时,按地区的细分数据才是关键。2026 年 1 月 1 日至 4 月 1 日期间,在 210 万次页面渲染上,我用 Chrome Lighthouse CrUX 数据集 和我自己的 Workers Analytics Engine 遥测数据测量结果如下:
| 地区 | Vite SPA p75 LCP | TanStack Start p75 LCP | 收益 |
|---|---|---|---|
ENAM(US East) | 1.52 s | 0.81 s | 47% |
ENAM(US West) | 1.58 s | 0.84 s | 47% |
WEU(London) | 1.74 s | 1.53 s | 12% |
WEU(Frankfurt) | 1.71 s | 1.48 s | 13% |
APAC(Singapore) | 1.84 s | 1.42 s | 23% |
APAC(Tokyo) | 1.79 s | 1.36 s | 24% |
ENAM 上 47% 的 LCP 收益是门面数据;WEU 上 12% 的收益是需要说明的部分。不对称来源于冷启动 TTFB。根据 web.dev TTFB 指南,TTFB 是唯一一个属于部署成本的 CWV 指标。在 ENAM 上,我的 TanStack Start Worker 冷启动平均 18ms;在 WEU 上,平均 49ms,因为 WARM 实例池更小。Vite SPA 没有 Worker——每次都是 CDN 边缘静态 HTML 响应,TTFB 恒定 8–12ms。
ENAM 上实现 47% 收益的配置
// vite.config.ts — Vite 6.0 + TanStack Start 1.0.0
// 在 v1.0 GA 之前,SSR 流式渲染配置需要单独的插件。
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import { cloudflare } from "@cloudflare/vite-plugin";
export default defineConfig({
plugins: [
cloudflare(),
tanstackStart({
ssr: {
// 当 <head> 解析完成后立即流式输出 HTML 壳
streaming: true,
// 在 Worker 隔离环境中预热 server functions
preload: ["src/server/*.ts"],
},
}),
],
build: {
// 将仅服务端依赖代码分割出客户端 Bundle
rollupOptions: {
output: {
manualChunks: {
"react-core": ["react", "react-dom"],
"tanstack-router": ["@tanstack/react-router"],
},
},
},
},
});
此配置适用于 Cloudflare Workers + TanStack Start + 流式 SSR。在 Node.js 部署上不适用,需修改——cloudflare() 插件是 Workers 专用的。Node.js 的替代配置见 TanStack Start 部署文档。
测量方法——以及方法论上的缺陷
我向同一域名发布了 TanStack Start,然后使用 Search Console URL Inspection API 每周采样 240 个 URL,再将 CrUX 现场数据与我自己的 Workers Analytics Engine 追踪关联起来。缺陷在于:我同时改了两件事——框架和图片分发。在迁移到 TanStack Start 的同时,我还开启了新构建版本上的 Cloudflare Image Resizing 功能。对于 Hero 图密集的页面,Image Resizing 单独就能将 LCP 降低 18%,所以 33% 里有框架的功劳,也有图片流水线的功劳。根据我保留在 Vite SPA 上的一个未开启 Image Resizing 的 holdback 组,我估计其中 22% 来自框架,11% 来自图片流水线。
65% Bundle 缩减并非免费
65% SSR 关键 Bundle 缩减是我反复引用的数字,它是真实的,但并非免费的。代价是 TanStack Start 交付了更多的代码路径——server functions、水合边界、路由状态——而懒水合只对首屏渲染有帮助。在一个 7 页 SaaS 应用上,节省是巨大的。在一个 200 页有深度链接的应用上,每条路由的节省更小。
| 技术栈 | 首屏 JS(gzip) | 水合后 JS | 首次渲染传输量 |
|---|---|---|---|
| Vite SPA | 184 KB | 184 KB | 184 KB |
| TanStack Start(仅 SSR) | 64 KB | +140 KB 水合后 | 64 KB(首次绘制) |
| TanStack Start(流式) | 71 KB | +140 KB 水合后 | 71 KB |
优化前: 首次绘制前传输 184 KB。优化后: 首次绘制前传输 64–71 KB,其余 140 KB 延迟传输。首次绘制 Bundle 是 LCP 的关键指标。水合后 Bundle 是 INP 的关键指标。在 TanStack Start 方案中,两者都有改善,但原因各不相同。
INP 收益来自水合策略,不是 Bundle 体积
根据 TanStack Router 文档,Router 1.135 带来了"选择性水合"——将视口外路由的水合推迟到进入视口时。这正是 36% p75 INP 收益(220ms → 142ms)的机制。INP 改善不来自 Bundle 缩减,而是来自用户交互窗口期间主线程工作量减少。
// app/routes/index.tsx — TanStack Start 1.0 + TanStack Router 1.135
// 视口上方路由的选择性水合默认开启。
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/")({
component: LandingPage,
loader: async () => {
// Server function 运行在 Worker 中,而非浏览器中
return await fetchPricing();
},
// 将视口外路由的水合推迟到交互触发
defer: {
enable: true,
},
});
function LandingPage() {
const pricing = Route.useLoaderData();
return (
<main>
<h1>{pricing.headline}</h1>
{/* 屏幕外卡片在交互时再水合 */}
<Suspense fallback={<Skeleton />}>
<PricingCards data={pricing.cards} />
</Suspense>
</main>
);
}
在 v1.135 之前,这需要一个自定义水合边界;现在默认开启。此方案适用于内容密集的落地页,但不适用于高度交互的单页应用——后者从相反的策略(积极水合)中受益更多。
限制 WEU 收益的 TTFB 悬崖
WEU 上 12% 的 LCP 收益对比 ENAM 上 47% 的 LCP 收益,这是意想不到的发现。根据 Cloudflare Workers 文档,每个 Worker 在空闲后首次请求都有"冷启动"成本。在 ENAM 上,我的 Worker fleet 平均冷启动 18ms。在 WEU 上,更小的 fleet 平均冷启动 49ms。Vite SPA 根本没有 Worker——每个页面都是 CDN 边缘静态 HTML。
导致混乱的数据
直到第 6 周我才拿到的数据:我的 WEU Worker fleet 是按 ENAM 18ms 冷启动配置的,但 Cloudflare 的自动扩缩在不同地区表现不同。修复方案是在 wrangler.toml 中加两行:
# wrangler.toml — TanStack Start 1.0.0 / Cloudflare Workers 2026.1.0
# 改之前,WEU p75 冷启动为 49ms
[placement]
mode = "smart" # 根据流量模式分配实例
优化前: WEU p75 冷启动 49ms,p75 LCP 1.74s。优化后: WEU p75 冷启动 28ms,p75 LCP 1.53s。21ms 冷启动收益转化为 WEU 上 12% 的 LCP 改善,因为 TTFB 和 LCP 高度相关。此方案适用于 Workers + TanStack Start,但不适用于 Node.js 部署——placement 指令是 Cloudflare 专用的。
何时选哪个:诚实的决策矩阵
问题不是"哪个更快",而是"在你的工作负载下哪个更快"。基于 91 天的数据,以下是诚实的矩阵:
| 工作负载 | Vite SPA | TanStack Start | 胜出方 |
|---|---|---|---|
| 单一区域 SaaS(<500ms RTT) | 良好 | 更优 | TanStack Start |
| 多区域 SaaS,图片密集 | 可接受 | 最优(配合 Image Resizing) | TanStack Start |
| 单页应用,无需 SEO | 更优 | 杀鸡用牛刀 | Vite SPA |
| SEO 落地页,低流量 | 可接受 | 更优 | TanStack Start |
| SEO 落地页,突发流量 | 风险高 | 最优(流式渲染 + 缓存) | TanStack Start |
| 内部工具,无需 SEO | 最优 | 杀鸡用牛刀 | Vite SPA |
更多框架选型和 Bundle 体积的完整讨论,参见 Vite vs TanStack Start:何时升级实战笔记 和 TanStack vs Next.js 2026 SEO 生产对决 数据。
利益披露: 与所评测工具无任何实质利益关系。Cloudflare Workers、Vite 和 TanStack Start 均按标价购买。本报告中的 SaaS 应用运行在 TanStack Ship 上,这是我维护的生产级 starter——当前构建配置见定价页。测试环境为 Vite 6.0 + TanStack Start 1.0.0,2026 Q1。实际结果因地区、图片流水线和第三方标签数量而异。