性能预算通过为加载时间、JS 大小和 Core Web Vitals 设定明确限制,并加入快速审计与先修规则,帮助保持 Web 应用的速度。

性能预算是在构建前达成的一组限制。它可以是时间限制(页面感觉有多快)、大小限制(你部署多少代码),或者简单的上限(请求数、图片、第三方脚本)。超过限制就被当作未满足的需求,而不是“以后再修”的事情。
速度变慢通常是因为发布是累加的。每个新组件都会增加 JavaScript、CSS、字体、图片、API 请求,并给浏览器带来更多工作。即便是微小改动也会累积,直到应用感觉沉重,尤其是在中端手机和较慢网络上,而那里是大多数真实用户所在的环境。
单靠意见不会保护你。一个人说“我的笔记本上感觉还行”,另一个说“很慢”,团队开始争论。预算会终结争论,把性能变成可测量和可强制的产品约束。
这也正是 Addy Osmani 的思路:把性能当作设计约束和安全规则来对待。你不会“尝试”保持安全或“希望”布局看起来好。你设定标准、持续检查,并阻止破坏它们的改动。
预算同时解决了几个实际问题。它们让权衡变得明确(添加功能意味着要在别处付出代价)、尽早捕捉回归(修复成本更低)、并给每个人相同的“够快”定义。它们也减少了发布前常见的恐慌式修补。
比如这样的场景:你为某个仪表盘视图加入了一个复杂的图表库。它随包一起下发给所有用户,增大了主包,并推迟了首屏呈现。没有预算,这种情况会因为“功能可用”而被忽略;有了预算,团队必须做出选择:懒加载图表、替换库或简化视图。
当团队能用聊天驱动的构建工作流(比如 Koder.ai)快速生成和迭代应用时,这点尤为重要。速度很好,但也容易在不注意的情况下引入额外依赖和 UI 花哨。预算能防止快速迭代变成慢体验。
当你把一切都量化但没人真正负责时,性能工作就会失败。选择一个对真实用户重要的页面流程,把它作为预算的锚点。
一个好的起点是影响转化或日常工作的主要路径,比如“首页到注册”、“登录后的仪表盘首屏”或“结账与付款确认”。选一个具有代表性且频繁出现的路径,而不是边缘用例。
你的应用不会只在你的笔记本上运行。在一台快机器上看起来还行的预算,在中端手机上可能感觉很慢。
先决定一个目标设备类别和一个网络配置,并把它写成每个人都能复述的一句话。
例如:过去 2–3 年的中端 Android 手机,4G 网络(不是办公室 Wi‑Fi)下的移动场景,测量冷启动以及一次关键导航,在大多数用户所在的同一区域运行。
这不是要你选最差的情况,而是选择你能实际为之优化的常见情形。
只有可比的数字才有意义。如果一次测试是“带扩展的 MacBook 上的 Chrome”,下一次是“节流的移动浏览器”,你的趋势线就是噪声。
选择一个基线环境并在预算检查中坚持使用:相同的浏览器版本、相同的节流设置、相同的测试路径和相同的缓存状态(冷或热)。如果使用真实设备,请使用相同型号。
然后把“够快”用行为来定义,而不是完美演示。例如:“用户可以很快开始阅读内容”或“登录后仪表盘感觉响应流畅”。把这些翻译成一个或两个用于该旅程的指标,再围绕它们设定预算。
预算在同时覆盖用户感受和团队可控项时效果最好。一套好的预算将体验指标(“感觉快吗?”)与资源和 CPU 限制(“为什么慢?”)结合起来。
这些指标记录页面对真实用户的表现。最有用的通常直接映射到 Core Web Vitals:
时间预算是你的北极星,因为它们对应用户的挫败感。但它们不总是告诉你具体如何修复,因此你还需要下面的预算类型。
这些更容易在构建和审查时强制执行,因为它们具体可测。
重量预算限制总 JavaScript、总 CSS、图片重量和字体重量。请求预算限制请求总数和第三方脚本,减少网络开销和来自标签、组件与跟踪器的“惊喜”工作。运行时预算限制长任务、主线程时间和 hydration 时间(尤其是 React),常常解释了为什么页面在中端手机上“感觉”慢。
一个实用的 React 示例:包大小看起来没问题,但新加入的轮播组件增加了繁重的客户端渲染。页面加载了,但在点击筛选时感觉卡顿,因为 hydration 阻塞了主线程。运行时预算例如“启动期间没有超过 X ms 的长任务”或“在中端设备上 hydration 在 Y 秒内完成”可以在重量预算失效时捕捉到问题。
最强的做法是把这些视为一个系统:体验预算定义成功,大小/请求/运行时预算让发布更诚实并使“发生了什么变化?”容易回答。
如果限制太多,人们会停止关注。选 3 到 5 个与你用户感受最相关且你能在每个 PR 或发布中测量的预算。
一个实用的入门集合(之后再根据情况调整数值):
两个阈值让事情保持理智。“警告”表示你在漂移。“失败”阻止发布或需要明确的批准。这让限制真实存在,同时不至于造成持续的火警。
把预算写在一个共享位置,这样在忙碌发布时没人再争论。保持简短且具体:涵盖哪些页面或流程、在哪里运行测量(本地审计、CI、预发布构建)、使用哪个设备与网络配置,以及指标如何定义(现场 vs 实验室,gzip vs 原始,路由级别 vs 整个应用)。
先从可重复的基线开始。选择一到两个关键页面,在相同设备配置和网络上多次测试。至少运行三次并记录中位数,以免个别异常跑次影响判断。
使用一个简单的基线表,包含用户指标与构建指标。例如:该页面的 LCP 与 INP,以及构建的总 JavaScript 大小和图片总字节。这会让预算更真实,因为你看到的是实际下发的内容,而不仅是实验室猜测的数据。
把预算设定得比今天稍好一点,而不是不切实际的数字。一个稳妥的规则是比当前中位数提升 5%–10%。如果基线 LCP 是 3.2s,不要直接跳到 2.0s,从 3.0s 开始,然后在证明能保持住后再收紧。
在每次发布前加入一个快速检查,确保用户看到之前就被检测到。保持检查足够快以免被跳过。一个简单做法是:对约定的页面运行一次单页审计,如果 JS 或图片超出预算则失败构建,将每次提交的结果存档以便追踪变化,并且始终测试相同的 URL 模式(不要用随机数据)。
每周审查违规,而不是等到有人抱怨才看。把违规当作 bug:找出导致它的变更,决定现在要修什么,什么放到日程里。缓慢收紧预算,只有在你能在几次发布中保持住后才逐步收紧。
当产品范围发生变化时,有意识地更新预算。如果你加入了新的分析工具或重量级功能,写下增长了什么(大小、请求、运行时)、你会如何在后续偿还,以及预算何时应恢复。
预算只有在你能快速检查时才有用。一次 10 分钟审计的目标不是证明一个完美数字,而是找出自上次正常构建以来发生了什么,并决定先修哪件事。
从一个代表真实使用的页面开始。然后每次运行相同的快速检查:
两个视图通常能在几分钟内给出答案:网络瀑布图和主线程时间线。
在瀑布图中,找出主关键路径上占主导的一次请求:巨大的脚本、阻塞字体或迟迟才开始请求的图片。如果 LCP 资源没有被尽早请求,无论服务器多快,都无法命中 LCP 预算。
在时间线中,寻找长任务(50 ms 或更长)。启动阶段附近的一簇长任务通常意味着首次加载时 JavaScript 太多。一个巨大的块通常是路由问题或共享包随时间增长。
快速审计会失败当每次运行都不相同。记录一些基础信息以便变化清晰:页面 URL 与构建/版本、测试设备与网络配置、LCP 元素描述、你跟踪的关键数字(例如 LCP、总 JS 字节、请求数)以及对最大罪魁的简短说明。
桌面测试适合快速反馈与 PR 检查。当你接近预算、页面感觉卡顿或用户以移动端为主时,使用真实设备。移动 CPU 会让长任务更明显,而许多“笔记本上看起来没问题”的发布在移动上会崩溃。
当预算失败时,最糟的做法是“把所有东西都优化一遍”。使用可重复的优先级顺序,让每次修复都有明确回报。
从用户最先注意到的开始,然后逐步微调:
一个团队发布了新的仪表盘,突然超出了 LCP 预算。他们不是先去调缓存头,而是发现 LCP 元素是一个全宽的图表图片。他们调整尺寸、使用更轻的格式,并只加载关键信息。接着发现一个大型图表库在每个路由都加载。他们把它只用于分析页面,并把一个第三方支持小工具延迟到首次交互后加载。一天内,仪表盘又回到预算内,下一次发布也有清晰的“发生了什么”记录。
最大的失败模式是把预算当成一次性的文档。预算只有在易于检查、难以忽视并与发布流程绑定时才有效。
大多数团队会陷入几个陷阱:
一个常见模式是某个“轻量”功能拉入了新库。包增大,LCP 在慢网络上变慢一秒,直到客户支持开始收到投诉才被注意到。预算就是为了在审查时让这种变更可见。
从简单开始并保持检查一致。选 2 到 4 个映射用户体验的预算,并逐步收紧。锁定你的测试设置并写下来。如果可以,跟踪至少一个真实用户信号,用实验室测试来解释“为什么”,而不是用来赢得争论。当一个依赖带来显著重量时,要求提交简短说明:它的代价是什么、替换了什么、为什么值得。最重要的是,把预算检查放到正常发布路径上。
如果预算感觉像持续摩擦,通常是因为它们对当前来说不现实,或没有与真实决策相连。先修这两点。
一个小团队在一周内发布了 React 分析仪表盘。起初感觉很快,但每周五的发布让它逐渐变重。一个月后,用户开始说首屏“卡住”,筛选也很卡。
他们停止争论“够快吗”,把预算写下来并与用户感知挂钩:
首次失败出现在两方面:初始 JS 包随着图表、日期库和 UI kit 的加入而增长;同时,仪表盘头图被替换为更大的文件,导致 LCP 超标。INP 变差是因为每次筛选都会触发沉重的重新渲染和主线程上的昂贵计算。
他们按顺序修复并取得快速成果,且防止重复回归:
通过调整并压缩图片、设置显式图片尺寸、避免阻塞性字体加载,让 LCP 回到线内。
通过移除未使用库、拆分非关键路由和懒加载图表,减少初始 JS。
通过 memoize 昂贵组件、对输入筛选去抖动、并将沉重工作移出热路径,改善 INP。
在每次发布中加入预算检查,如果某项指标破坏了预算,发布就暂停。
两次发布后,LCP 从 3.4s 降到 2.3s,INP 在同一测试设备上从约 350ms 改善到低于 180ms。
预算只有在人人能以相同方式遵循时才有效。保持简单、写下来,并把它作为发布流程的一部分。
选一小组适合你应用的指标,设定“警告 vs 失败”阈值,并精确记录如何测试(设备、浏览器、网络、页面/流程)。保存当前最佳发布的基线报告并清晰标注。决定什么算作有效例外,什么不算。
在每次发布前运行相同审计并与基线比较。如果出现回归,把它记录到你用来跟踪错误的地方,并把它当成破坏结账流程的 bug 而非“以后再做”的任务。如果带着例外发布,记录负责人和到期日(通常 1–2 个迭代)。如果例外不断续期,就需要真正讨论预算问题。
将预算提前纳入规划与估算:“这个屏幕会加入图表库,所以我们需要移除别的东西或懒加载它。”如果你用 Koder.ai (koder.ai) 构建,还可以在 Planning Mode 中事先写下这些约束,然后以更小的切片迭代,并在变更使应用超出上限时使用快照与回滚。关键不在于工具,而在于习惯:每个新功能都要为它带来的重量付出代价,否则就不下发。
A performance budget is a set of hard limits (time, size, requests, CPU work) that your team agrees to before building.
If a change exceeds the limit, treat it like a broken requirement: fix it, reduce scope, or explicitly approve an exception with an owner and an expiry date.
Because performance gets worse gradually. Each feature adds JavaScript, CSS, images, fonts, API calls, and third‑party tags.
Budgets stop the slow creep by forcing a tradeoff: if you add weight or work, you must pay it back (lazy-load, split a route, simplify UI, remove a dependency).
Pick one real user journey and one consistent test setup.
A good starter is something frequent and business-critical, like:
Avoid edge cases at first; you want a flow you can measure every release.
Start with one target that matches typical users, for example:
Write it down and keep it stable. If you change device, network, cache state, or the path you test, your trend becomes noise.
Use a small set that covers both what users feel and what teams can control:
A practical starter set is:
Use two thresholds:
This avoids constant fire drills while still making the limits real when you cross the line.
Do this in order:
Not always. Bundle size can be fine while the page still feels slow because the main thread is busy.
Common React causes:
Add a runtime budget (for example, limit long tasks during startup or set a hydration time cap) to catch this class of issues.
Fast generation and iteration can quietly add dependencies, UI flourishes, and third-party scripts that ship to everyone.
The fix is to make budgets part of the workflow:
This keeps fast iteration from turning into a slow product.
Timing metrics show the pain; size and runtime limits help you quickly find what caused it.
Pick 3–5 budgets first. Tune later based on your baseline and release history.
Treat the breach like a bug: identify the commit, fix or scope-reduce, and prevent repeats.