本文由 Huifer 撰写,他是独立开发者兼 TanStack Ship 的维护者。在过去的 14 个月里,我将同一套 Core Web Vitals 优化手册应用于 5 个生产环境的 TanStack Start 应用中——一个拥有约 3,200 名客户的 B2B 计费仪表盘、一个多租户分析工具、一个开发者仪表盘以及两个交易市场。现在每个应用的 Lighthouse 性能得分均在 95+,在模拟移动 4G 网络下,p75 LCP 稳定在 1.4 s 以内,p75 INP 在 120 ms 以内。本文将详述这套手册、相关数据以及发生过的性能回退。
验证数据源:web.dev — Web Vitals · Google Search Central — Core Web Vitals · Chrome for Developers — INP · TanStack Start documentation · TanStack Router SSR streaming
最后更新:2026-08-28 · 更新日志
**太长不看 (TL;DR):**2026 年的 Core Web Vitals 包含 LCP、CLS 和 INP——这些是与 Google 排名挂钩的指标,用于衡量真实用户的加载速度、布局稳定性和交互响应速度。根据 Google 公布的标准,第 75 百分位(p75)的目标是 LCP 低于 2.5 秒,CLS 低于 0.1,INP 低于 200 毫秒。能让 TanStack Start SaaS 在生产环境中达到 95+ Lighthouse 性能得分的优化手册包含 8 个步骤:制定严格的性能预算、延迟加载非关键 JS、通过 TanStack Router 进行 SSR 流式传输、为字体和图片预留布局空间、压缩并预连接静态资源、在边缘节点使用 Cloudflare Cache API、隔离水合(hydration)开销,以及持续监控 p75 RUM。完整技术栈请参阅 TanStack Ship 功能;深入的路由优化阅读推荐 TanStack Router 最佳实践。
Core Web Vitals 2026 优化指南:让 5 个 TanStack Start SaaS 达到 95+ 分的 8 步操作手册
自 2021 年起,Google 的排名系统就开始将 Core Web Vitals 纳入考量,但到了 2026 年,这些阈值不再只是平局决胜的加分项,而是变成了硬性及格线。虽然页面体验信号依然作为相关性的补充,但未达到 p75 阈值的网站现在会在竞争激烈的搜索引擎结果页(SERP)中遭遇明显的排名下滑。我亲眼看到我的三个客户仅进行了一次针对 Lighthouse 的重构,排名就从第 2 页跃升至第 1 页。本文就是我现在默认采用的优化手册。
2026 年的 Core Web Vitals 是什么——以及为什么它们现在如此重要
Core Web Vitals 的三大指标衡量了用户能切身感受到的三个维度:最大内容的呈现速度有多快、页面在加载时会发生多大幅度的跳位,以及用户触控后页面的响应速度有多快。根据 Google 的 Web Vitals 官方文档,基于真实用户的第 75 百分位官方阈值设定如下:
- LCP (Largest Contentful Paint) — 达到“良好”需低于 2.5 s
- CLS (Cumulative Layout Shift) — 达到“良好”需低于 0.1
- INP (Interaction to Next Paint) — 达到“良好”需低于 200 ms
第 75 百分位法则至关重要。Google 从不看平均值;它关注的是处于你网站流量第三四分位数的用户体验。这意味着假如你有 25% 的用户体验极差,你的“页面体验”信号依然可能是“良好”——但仅此而已。如果你的中位数用户使用的是一台速度较慢的手机,那么你的 p75 用户使用的就是一台更差的手机,而这恰好就是 Google 所测量的人群。
在 2025 年第四季度,我的一个分析类应用在这个观点上让我付出了惨痛代价。Lighthouse 在桌面端显示性能得分高达 92 分,但 CrUX 现场数据却无情地将该网站标记为“需要改进”,因为移动端 p75 LCP 长达 3.1 s。事实证明,桌面端的跑分只是虚荣指标;现场的 p75 才是最终真相。
适用于每一个 TanStack Start SaaS 的 8 步操作手册
以下操作手册是我目前在 TanStack Ship 中默认应用的标准工序。其中的每一步都不够花哨;它们只是那些枯燥而可重复、却能产生复利效应的模式。
第一步:在编写代码前设定性能预算
预先把性能指标约束好作为开发预算,是你投入在决策环节中所能得到的最高杠杆率抉择。以下是我每个新应用都采用的预算标准:
| 指标 | 目标 | 失败底线 |
|---|---|---|
| LCP (p75 移动 4G) | ≤ 1.8 s | > 2.4 s |
| CLS (p75) | ≤ 0.05 | > 0.09 |
| INP (p75) | ≤ 150 ms | > 195 ms |
| 初始 JS 包体积 | ≤ 90 KB gzip | > 110 KB gzip |
| LCP 图片传输大小 | ≤ 60 KB | > 100 KB |
这份预算标准在每个 PR 的 CI 环节中由 Lighthouse CI 强制执行。当某条 PR 把 JS 包体积顶到了 90 KB gzip 以上,它就会被自动拦截。这一项规则让我成功避免了 4 次险些悄无声息带到线上的性能倒退。
第二步:延迟加载每一个非关键 JavaScript
在 TanStack Start 应用中,削减首次绘制所需的 JavaScript 是优化 LCP 见效最快的手段。默认的 TanStack Start 模板会积极地下发完整的路由图谱和所有路由加载器的分包界限;这里的优化技巧是,在维持该机制的前提下,将真实的 loader 回调函数及关联依赖转为动态导入(dynamic imports)。
// src/routes/dashboard.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
// Loader 在 SSR 期间于服务端运行 —— 这段代码不会发送到客户端
loader: async ({ context }) => {
const { getDashboardData } = await import('~/server/dashboard')
return getDashboardData(context.tenantId)
},
component: DashboardPage,
})
function DashboardPage() {
// useLoaderData() 是类型安全的;SSR 结束后数据已存在缓存中
const { stats } = Route.useLoaderData()
return <StatsGrid stats={stats} />
}
这里的加载器函数仅在 SSR 流式渲染期间于服务端运行——它会从客户端包中被完全移除。客户端接收到的只剩 JSON 数据。在我的 B2B 计费应用上,仅凭这一个小改动,仪表盘路由的客户端 JS 就从 184 KB gzip 缩减到了 71 KB gzip。
第三步:通过 TanStack Router 的 loader 管道流式传输 SSR
TanStack Router 的加载器管道会随着数据的解析情况渐进式地将 HTML 进行流式传输。浏览器首先接收 <head>,然后是页面外壳(shell),接着在各自的加载器完成时逐个接收每个板块。根据 TanStack Router 文档,当响应返回并附带 Transfer-Encoding: chunked 时,该流式行为默认处于启用状态。
给 LCP 带来的红利在于:LCP 元素——通常是由加载器数据渲染的首屏大图或标题大字——能够随第一个 chunk 提早下发,而不再需要苦等完整文档生成。在这款分析应用上,流式传输的开启将 p75 LCP 从 2.6 s 缩减到了 1.4 s。
第四步:为字体和图片预留布局空间
CLS 的诱因几乎总是以下三点:Web 字体在文本渲染完毕后才加载出来、图片缺少显式的尺寸数据,或者视口上方被动态注入了内容。修复手段具有机械化的标准解:
<!-- 务必对所有图片显式声明 width 与 height -->
<img
src="/hero.webp"
width="1200"
height="600"
alt="Dashboard preview"
fetchpriority="high"
/>
<!-- 预加载核心关键字体文件 -->
<link
rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<!-- 使后备字体的尺寸指标与 Web 字体相互匹配 -->
<style>
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
}
html { font-family: 'Inter', 'Inter Fallback', sans-serif; }
</style>
这里的 size-adjust 和 ascent-override 参数均取自于 Google Fonts CSS API v2——使后备字体的属性与 Web 字体相匹配,意味着当字体替换发生时浏览器不再需要进行布局重排(reflow)。在 5 款应用中,这种模式在没有更改任何视觉设计的前提下将 CLS 从 0.12 降到了 0.03 以下。
第五步:压缩、预连接并采用现代化图片格式
SaaS 落地页上的首屏英雄大图往往就是最大的 LCP 元素。仅需三个设置就能使图片的传输体积缩减 60% 至 80%:
- 优先输出
avif或webp而不是png/jpg(借助<picture>标签支持自动降级) - 对静态资源主机发起预连接,使 TLS 协商环节在图片发出请求前预先完毕
- 在主 LCP 图片设置
fetchpriority="high",然后给其他无关元素设置loading="lazy"
<head>
<link rel="preconnect" href="https://imgs.tanstackship.com" crossorigin />
<link rel="dns-prefetch" href="https://imgs.tanstackship.com" />
</head>
组合应用这三项改动,将该分析应用落地页加载的大图体积从 240 KB 直降到了 48 KB,使 p75 LCP 进一步缩减了 200 ms。
第六步:利用 Cloudflare Cache API 在边缘节点缓存 HTML 代码
对于 95% 的访客来说,SaaS 落地页展示的内容都是一致的。这些 HTML 代码理应被缓存在边缘节点。在 Workers 处理函数内结合 Cloudflare Cache API 是最直截了当的解法:
// src/server/index.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const cache = caches.default
const cached = await cache.match(request)
if (cached) return cached
const response = await renderToStream(request, env)
// 仅缓存请求格式为 HTML 且不包含 session cookie 的 GET 请求
if (
request.method === 'GET' &&
response.headers.get('content-type')?.includes('text/html') &&
!request.headers.get('cookie')?.includes('session=')
) {
response.headers.set('Cache-Control', 'public, max-age=300, s-maxage=3600')
await cache.put(request, response.clone())
}
return response
},
}
在同区域内,被边缘缓存过的 HTML 能够直接将 TTFB 压至 30 ms 以内,而这也是一切 LCP 指标优化的底线。对于必须动态个性化渲染的私人页面,可以将包含当前会话 ID 的字段设为 cache key(缓存键),这样也可以实现按租户的精细化缓存。
第七步:在复杂沉重的路线上将水合成本隔离
INP 是我在 2025 年最容易低估的指标。INP 衡量的是从用户发生交互到页面下一次呈现出该交互结果之间的延迟。在 TanStack Start 应用上,首次 INP 延迟冲击往往来自于水合(hydration)——即页面加载完毕后,React 准备接管事件处理器并处理挂起客户端任务的那一刻。
修复方法是:把首屏以下(below-the-fold)组件的水合过程延迟,直到它们滚动进入视口范围。TanStack Start 非常契合官方文档中介绍的 IntersectionObserver 模式:
// src/components/LazyHydrate.tsx
import { useEffect, useRef, useState } from 'react'
export function LazyHydrate({ children, fallback }: { children: React.ReactNode; fallback: React.ReactNode }) {
const ref = useRef<HTMLDivElement>(null)
const [visible, setVisible] = useState(false)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting) setVisible(true) },
{ rootMargin: '200px' }
)
if (ref.current) observer.observe(ref.current)
return () => observer.disconnect()
}, [])
return <div ref={ref}>{visible ? children : fallback}</div>
}
在开发者仪表盘中,这种设计手段硬生生将超出 Google 阈值的 280 ms INP 降到了 110 ms。
第八步:监测 p75 RUM,而不是一味地看重 Lighthouse 实验室数据
Lighthouse 运行在被强制限制网速的虚拟机模拟连接下。它是一个非常有用的 CI 检查工具,但它绝不代表用户的真实体验。唯一能反映用户主观体验的指标,是通过 RUM(真实用户监控)工具捕获并给到你的生产环境中第 75 百分位的真实用户现场数据。具体的 RUM 实现流程是我下一篇文章——2026 年 SaaS 真实用户监控指南——的主题,但核心运作原则是:上线第一天就必须部署 RUM,将 p75 性能警报的阈值设在预算标准的 90%,并把每一次性能倒退当作 P1 级的故障来优先处理。
实施操作手册后的数据表现
以下数据来源于基于 CrUX 现场数据与 Cloudflare Analytics Engine RUM 数据管线对 5 款生产环境应用进行的 p75 移动端 4G 模拟测试:
| 应用 | 优化前 LCP | 优化前 INP | 优化前 CLS | 优化后 LCP | 优化后 INP | 优化后 CLS |
|---|---|---|---|---|---|---|
| B2B 计费仪表盘 | 3.4 s | 280 ms | 0.14 | 1.2 s | 95 ms | 0.02 |
| 多租户分析工具 | 2.9 s | 240 ms | 0.11 | 1.1 s | 88 ms | 0.02 |
| 开发者仪表盘 | 2.6 s | 220 ms | 0.09 | 1.0 s | 80 ms | 0.01 |
| 交易市场 A | 3.1 s | 260 ms | 0.13 | 1.3 s | 102 ms | 0.03 |
| 交易市场 B | 2.8 s | 235 ms | 0.12 | 1.2 s | 96 ms | 0.02 |
如今,每一项指标在 Google 所界定的阈值中都稳稳落于“良好”及格线之内,每一个应用在 CI 环境下也均保持了 95+ 的最高级别 Lighthouse 得分。需要提前说明的限制前提是:我并没有在 3G 降级网络上进行过测试,而且这 5 个应用全都采用了相同的全栈技术系(TanStack Start + Cloudflare Workers + D1),因此这套优化手册对其他运行时的可迁移性还有待验证。
我所踩过的坑以及它们带给我的启示
这套手册并不是什么一劳永逸的方法论。随着功能迭代的交付,性能必然总是不断地发生倒退。在过去一年,有 3 次性能回归问题给了我极其惨痛的教训:
- 某个营销网站在 2026 年第一季度新增了一个第三方聊天挂件。 该挂件在关键渲染路径上增加了 47 KB 的阻塞 JS,导致 p75 INP 在 48 小时内从 95 ms 飙升至 240 ms。修复方法:在首次交互后实施懒加载。
- 某个仪表盘引入了一个过于笨重的图表库。 其初始 chunk 将整个仪表盘路由的 JS 包从 71 KB 推高到了 138 KB,这导致 p75 LCP 从 1.2 s 退化至 2.1 s。修复方法:替换为一个更轻量的图表库,并设置只有在鼠标悬停时才发起的动态导入。
- 某个交易市场将动态广告插槽安放在了首屏。 直接导致 CLS 从 0.02 猛增到 0.18。修复方法:提前为每个广告槽预留一个固定高度的容器。
每一次性能倒退都是由 RUM 警报检测出来的,而不是 Lighthouse。当实验室内还处于绿灯状态时,现场实际数据早已亮起红灯。如果你只满足于依赖 CI 中运行的 Lighthouse,那么上述三种倒退你统统都会错过。
TanStack Ship 开箱即用的特性
TanStack Ship 是一款为独立开发者量身定制的 TanStack Start 启动模版,旨在让你省去从头编写的麻烦,直接无缝应用这套操作手册。它的模板自带如下功能模块:
- 预置本文手册中全部的 8 个优化步骤
- 通过 Lighthouse CI 在 CI 环节中强制执行的严格性能预算
- 预载完成的 TanStack Router SSR 流式传输模式
- 配置了合理默认设置的 Cloudflare Cache API 整合
- 立即可用的上文所述
LazyHydrate延迟水合组件 - 已经对接配置至 Cloudflare Analytics Engine 的 RUM 监控管线
想与其他 TanStack Start 模板并排对比,可以参阅 TanStack Ship 全面评测 或是 与 Shipfast 的对比分析。如果专门想了解 Cloudflare 的边缘缓存策略,Cloudflare Workers SSR 技术拆解 一文对流式传输进行了深入探讨。
2026 年的底线非常简单:如果你发布了一款未达到 CWV p75 阈值的 TanStack Start SaaS,那么在这个维度上表现优异的竞争对手势必会吞噬你的排名。上述手册正是达到这些及格线所必须付出的最小可行性工作量;而 TanStack Ship 则预先完成了所有底层配置工作,从而让你能将精力放在核心功能的交付上。