SaaS Performance Optimization: 2026 完整边缘优先指南

涵盖 CDN、边缘计算、D1、SSR 和 Core Web Vitals 的 SaaS 性能优化生产级完整指南,配有 TanStack Ship 经实战验证的默认配置。

Huifer
Huifer
June 26, 202610 min read
其他语言:Deutsch · English

本文由 Huifer 撰写,TanStack Ship 的独立开发者与维护者。我在 Cloudflare Workers + D1 上运行着 12 个以上的生产 SaaS 应用,三年来一直追求边缘侧 sub-200ms p95 延迟。本指南汇集了 TanStack Ship 14 个模块中所有开箱即用的默认配置——这些模式都经过真实付费客户的验证,而非理论基准。对于我尚未验证的规模(50k req/s、多区域故障转移),我会明确说明。

已验证来源: Cloudflare Workers Docs · Cloudflare D1 Docs · Web Vitals · TanStack Router · TanStack Ship GitHub

最后更新: 2026-06-26 · 更新日志


TL;DR

SaaS 性能优化不是单一问题——它是四层叠加的系统:网络层(CDN + 边缘计算)、数据库层(D1 + SQLite 模式)、前端层(TanStack Start SSR + 水合)、可观测层(真实用户监测 + 性能预算)。大多数创始人在第一时间优化了错误的层级。80/20 法则指向 CDN 和边缘缓存:在这个层级将 TTFB 降低 300ms,胜过任何前端微优化。之后是 D1 查询模式(索引、通过 Cloudflare Queues 批量写入)和 TanStack Start 的流式 SSR,可以带来下一个数量级的提升。本指南逐层讲解,配有 TanStack Ship 在生产环境中使用的精确配置。


为什么性能是收入问题,而非技术问题

在开始任何优化之前,我想先转变讨论的框架。性能不是 Lighthouse 标签页里的工程问题——它是转化率杠杆,在所有 SaaS 干预措施中投资回报率最高。

数学很残酷。B2B SaaS 中,LCP 每增加 100ms,转化率降低约 7%。B2C 结账流程中,同样的延迟对页面价值的影响幅度类似。Stripe 团队公开引用的内部数据显示,结账放弃率在总加载时间超过 2 秒后急剧攀升。在 800ms 到 1.5s p75 LCP 之间,每削减一毫秒都有可衡量的美元回报。

这个框架很关键,因为它决定了投资优先级。如果你的结账页需要 1.8s,首页只需 0.9s,你不会从拆分营销页代码开始——你会从结账页开始,因为收入就在那里。没有收入归因的性能优化只是工程自娱自乐。

我在每个 TanStack Ship 客户身上运行一个 sanity check:任何优化工作之前,先跑一个 7 天 RUM 基线,标记哪些路由带来收入(结账、注册、管理员的 MRR 仪表盘),然后按(延迟节省) × (每次请求收入)排序优化项。前三名通常来自 CDN 层和数据库写入路径——而不是前端框架选择。


SaaS 性能的四层结构

我将 SaaS 性能理解为四个相互正交的层级,大多数 SaaS 只优化其中之一:

1. 网络层(CDN + 边缘计算)

用户收到的第一个字节。包括 CDN 缓存命中率、边缘函数冷启动延迟,以及到源站的地理距离。这里一个配置改动就能将 TTFB 从 800ms 砍到 80ms。对几乎所有 SaaS 来说,这是杠杆最高的层级。

2. 数据库层(D1 / SQLite / Postgres)

查询延迟、索引覆盖范围和锁竞争。对 D1 来说,写锁是一个全局瓶颈——详见我的 D1 写锁事故复盘,完整的事故发生在 200 req/s。这个层级是大多数「我们遇到性能问题」工单的实际来源。

3. 前端层(SSR + 水合 + Bundle)

可交互时间、水合成本、JavaScript 包体积。TanStack Start 的流式 SSR 确实比客户端渲染 SPA 有优势——但前提是使用得当。400KB 的 JS 包仍然会摧毁你的 LCP,不管你的 SSR 有多好。

4. 可观测层(RUM + 性能预算)

无法衡量的东西无法优化。通过 Web Vitals 库进行的真实用户监测(RUM)是唯一反映真实客户的信号。合成 Lighthouse 运行用于捕捉回归,而不是作为主要的优化目标。

我看到的最常见错误:创始人在第 3 层(前端微调)上花 80% 的性能时间,而仅第 1 层就能通过一次配置改动带来同样的转化率提升。如果只优化一个层级,优化 CDN。


第 1 层:边缘网络与 CDN——80/20 法则

SaaS 性能优化中投资回报率最高的改动是正确配置 CDN。对于 Cloudflare 上的 TanStack Ship 客户,我默认发送的配置如下:

toml
# wrangler.jsonc
{
  "rules": [
    {
      "description": "Static assets — 1 year cache",
      "expression": "(http.request.uri.path ~ '\\.(css|js|woff2|png|jpg|svg|webp|avif)$')",
      "actions": {
        "cache": { "ttl": 31536000 }
      }
    },
    {
      "description": "Dynamic HTML — stale-while-revalidate",
      "expression": "(http.request.uri.path eq '/' or http.request.uri.path ~ '^/blog')",
      "actions": {
        "cache": {
          "ttl": 300,
          "serve_stale": {
            "status": [200],
            "stale_while_revalidate": 86400
          }
        }
      }
    }
  ]
}

stale_while_revalidate 块是让你在保持缓存新鲜度的同时又不受缓存未命中惩罚的技巧:用户立即看到旧版本,同时后台请求刷新缓存。这就是为什么即使在部署之后,300s TTL 对用户来说感觉是即时的。

静态资源缓存策略

静态资源(CSS、JS、字体、图标)应该缓存一年,通过文件名中的内容哈希进行指纹识别。Cloudflare 的边缘缓存在全球分布,拥有 330 多个节点,对大多数全球用户约 50ms p50 的延迟。CDN 在承担工作——你的源站在休息。如果尚未使用 Cloudflare,请参见 CDN 选择和配置指南 进行提供商对比。

动态内容的边缘缓存

动态 HTML 是更复杂的情况。当内容是用户特定的时候(已登录的 MRR 仪表盘、个性化定价),纯 HTML 缓存会失效。我在 TanStack Ship 中默认发送的模式:

  1. 对匿名营销路由缓存 300s,同时开启 stale-while-revalidate。
  2. 对认证路由完全绕过缓存(cookies: tb_session)。
  3. 对读取密集型且可容忍数据陈旧的路由,使用 Workers Cache API 缓存 API 响应 0-60s。

关键是路由分段——TanStack Router 基于文件的路由使这变得简单,因为缓存规则可以针对特定的路由树。

关于 Vercel Edge 和 AWS CloudFront:两者都是可靠的选择,我都在上面部署过。Vercel 的边缘网络速度快,但与 Next.js 绑定;CloudFront 的覆盖范围更广,但配置面更大。Cloudflare 将 CDN + Workers + D1 + R2 集于一身,这就是 TanStack Ship 选择它的原因——供应商越少,集成 bug 越少。


第 2 层:数据库性能——D1 和 SQLite 模式

Cloudflare D1 是运行在边缘的 SQLite,所以它很快,但有写锁上限。我在其他地方详细介绍了 D1 查询优化技术;我发送的生产级默认配置如下:

多租户 SaaS 的索引策略

sql
-- 每个租户作用域的表都需要 (tenant_id, created_at) 的复合索引
CREATE INDEX idx_subscriptions_tenant_created
  ON subscriptions(tenant_id, created_at DESC);

-- 外键始终应该有索引
CREATE INDEX idx_invoices_subscription_id
  ON invoices(subscription_id);

-- WHERE 子句中使用的 JSON 列需要生成列 + 索引
ALTER TABLE subscriptions ADD COLUMN plan_tier TEXT
  GENERATED ALWAYS AS (json_extract(metadata, '$.tier')) VIRTUAL;
CREATE INDEX idx_subscriptions_plan_tier ON subscriptions(plan_tier);

复合索引 (tenant_id, created_at DESC) 是 SaaS D1 Schema 中最常见的缺失索引。每个对租户作用域表的列表查询都需要它。GENERATED ALWAYS AS 模式让你无需复制数据即可对 JSON 列建立索引。

写锁陷阱

如果你的 SaaS 在 D1 上持续超过 50 次写入/秒,就会撞上 SQLite 的全局写锁。这不是 D1 的 bug——这是 SQLite 的工作方式。修复方案是我在 D1 写锁事故复盘 中记录的队列 + 工作流模式。我没有在 50k req/s 规模下测试过——我运行的部署峰值约为 600 writes/sec——但这个模式在整个 D1 范围内是成立的。

对于 TanStack Ship 客户,计费模块默认发送这种基于队列的批处理,所以我在 2026 年 2 月 200 req/s 时遇到的故障模式在本模板中根本不会发生。


第 3 层:前端性能——TanStack Start SSR

TanStack Start 的流式 SSR 确实不错,但前提是避免常见的陷阱。

正确的流式 SSR

TanStack Start 按准备好顺序流式传输 HTML 块。第一块应该包含折叠线以上的英雄区和主要 CTA——折叠线以下的内容稍后流式传输。陷阱:不要在 SSR 阶段 await 会阻塞第一块的数据库调用。使用 Suspense 边界来保持英雄区骨架流式传输,同时较慢的查询解析完成。

Bundle 卫生

400KB 的 JavaScript 包仍然会摧毁你的 LCP,不管服务器响应多好。我在 TanStack Ship 中执行的卫生规则:

  • 路由级代码分割(TanStack Router 自动完成)。
  • 不要 import lodash——使用 lodash-es 按方法导入或原生等价物。
  • 日期库:优先使用原生 Intl API 而不是 Moment。
  • 图标库:激进地 tree-shake;一个图标 = 一个组件。

图片优化管线

对于 SaaS,图片通常是最重的负载。我的管线:

  1. 在支持的条件下源图片使用 AVIF,回退为 WebP。
  2. 使用 <img> 加 srcset 实现响应式尺寸。
  3. 用 loading="lazy" 懒加载折叠线以下的所有内容。
  4. 折叠线以上的关键图片使用 <link rel="preload" as="image">。

关于 Core Web Vitals 更深入的覆盖,Core Web Vitals 优化指南 涵盖测量端;Lighthouse 95 优化 文章涵盖在合成分数上真正见效的具体策略。

我在 2025 年遇到的一个陷阱:来自合成 Lighthouse 的「快」LCP 分数可能掩盖了糟糕的真实用户 LCP,因为 Lighthouse 从单一数据中心运行,网络条件理想。在宣布胜利之前,始终将 Lighthouse 与 RUM 配对。合成分数是回归检测器;RUM 百分位数才是真相。


第 4 层:可观测性——性能预算

防止性能回归的最便宜保险是在 CI 中强制执行的性能预算。模式如下:

typescript
// performance-budget.ts
export const budget = {
  // Bundle size budgets (gzipped)
  maxJsBundleKb: 180,
  maxCssKb: 40,
  maxInitialImageKb: 120,

  // Core Web Vitals (p75)
  maxLcpMs: 1800,
  maxInpMs: 200,
  maxCls: 0.05,

  // Server timings
  maxTtfbMs: 200,
  maxDbQueryMs: 50,
};

// In CI, fail the build if any budget is exceeded
export function checkBudget(measurements: typeof budget) {
  for (const [key, limit] of Object.entries(budget)) {
    const actual = measurements[key as keyof typeof budget];
    if (typeof actual === 'number' && actual > limit) {
      throw new Error(`Performance budget exceeded: ${key} = ${actual}, limit = ${limit}`);
    }
  }
}

我在每个 pull request 上运行这个预算检查。只要任何特性分支超过任何预算,构建就失败。这一项单一添加所防止的性能回归比所有其他策略加起来还多。

关于更深入的可观测性栈——RUM 仪表盘、告警阈值、回归检测——性能监控指南 涵盖生产部署。

我推荐的部署顺序:首先部署 RUM(一个下午将 Web Vitals 接入路由组件),然后在 CI 中设置预算(一天),然后按 RUM p75 优化最慢的路由(持续进行)。没有预算的 RUM 给你数字但没有强制执行;没有 RUM 的预算给你不反映真实用户的合成数字。两者都需要。


TanStack Ship 性能栈

TanStack Ship 附带全部四层预配置。你不是从零开始,而是从生产默认配置开始。

默认发送的内容

  • CDN 规则 在 wrangler.jsonc 中:静态资源缓存、动态 HTML stale-while-revalidate、API 响应缓存。
  • D1 Schema 迁移 覆盖 14 个模块:每个租户作用域的表都有复合索引,每个 JSON 查询列都是生成的且有索引。
  • TanStack Start 路由 配有正确的 Suspense 边界和流式 SSR。
  • 性能预算 接入 CI;PR 有回归则构建失败。
  • 生产环境 RUM 通过 Web Vitals 实现,按路由提供 p75 仪表盘。

你需要配置的内容

每个 SaaS 的流量形态不同。你按路由调优缓存 TTL,调整 RUM 告警阈值,并为特定领域查询模式添加自定义索引。但你从生产默认配置开始,而不是从空白开始。

查看 TanStack Ship 功能 获取完整模块列表,或直接前往 定价 了解哪个阶段适合你。


结语

SaaS 性能优化是一个四层问题,而且各层相互叠加。CDN 节省 100ms + 数据库节省 100ms + SSR 节省 100ms,就是 300ms 的 LCP 改善——对于 B2B SaaS 结账来说大约是 20% 的转化率提升。这些都不需要重写;只需要正确的默认配置和防止回归的性能预算。

TanStack Ship 默认发送这些配置,因为我用惨痛教训学到了没有它们会发生什么。三年生产事故凝练成 14 个模块,让你从第一次部署就达到 sub-200ms p95。克隆免费入门版 →