前言
2026 年,Andrej Karpathy 公开了 autoresearch。对我最有启发的地方,不是训练小模型这件事本身,而是它把研究组织成了一套有边界、有基线、有保留和放弃规则的持续实验机制。
这套思路很适合迁移到前端性能优化领域。相比一次性的性能专项,它更强调先固定实验环境、限制改动范围、统一记录结果,再让 agent 在明确规则下持续提出假设、验证假设、保留有效改动。
在这套方法的指引下,我把前端性能优化组织成一套"固定实验室环境、限制改动范围、小步实验、统一记录结果、只保留被证明有效的改动"的持续优化系统。
从小处着手
第一次落地这套前端性能实验方法时,只选:
- 一个关键页面或关键交互路径
- 一个北极星指标
- 一类允许改动的优化面
推荐的上手方式:
- 落地页首屏加载
- 商品详情页首屏与交互
- 工作台首页首屏
- 编辑器内某条关键交互链路
不推荐第一次就做:
- "整个站点性能治理"
- "一口气把 LCP、INP、CLS、SEO、错误率都一起优化"
- "允许 agent 改所有前端和 Node 代码"
第一步:定义北极星指标
每轮实验只设一个主目标,推荐从下面选一个:
p75 LCPp75 INPp75 CLS- 首屏 JS 传输体积
- 某关键交互的 long task 总时长
推荐口径:
- 选一个最接近用户真实痛点的指标
- 对同一个实验周期保持口径不变
- 把其他指标降级为底线指标或辅助指标
一个简单模板:
- 主目标:商品详情页移动端
p75 INP - 底线指标:功能正确、错误率不上升、CLS 不变差
- 辅助指标:首屏 JS、长任务数、第三方脚本耗时
什么样的北极星指标是好的?
可以用下面这张表快速判断一个指标适不适合做每轮实验的主目标:
| 判断维度 | 好的指标 | 坏的指标 |
|---|---|---|
| 和用户体验的关系 | 直接反映用户体感,例如页面是否出来得快、点击后是否有响应、页面是否会跳动 | 只反映局部技术状态,和用户实际感受距离较远 |
| 稳定性 | 在相同环境下重复测试,结果相对稳定,可比较 | 噪声很大,多跑几次就明显波动,分不清是改动生效还是环境干扰 |
| 可操作性 | 改动之后能较快看到变化,适合指导下一轮实验 | 反馈很慢,或者变化太间接,难以指导单轮决策 |
| 是否容易"刷分" | 指标变好通常意味着体验真的变好 | 很容易通过技巧把数字做漂亮,但用户体验并没有改善 |
| 团队共识成本 | 团队容易理解,能围绕它达成统一判断 | 每次都要解释它代表什么,为什么它重要 |
常见的坏指标有几类:
- 噪声太大,例如没有固定数据状态的整页总耗时,或者没有统一交互脚本时的人手点击耗时
- 离用户体感太远,例如单纯的 bundle 体积、某个函数执行时间、某个接口返回时间
- 太容易被"刷分",例如通过延后关键内容渲染,让某个数字变好看,但用户依然觉得页面不可用
- 反馈周期太长,无法支撑高频实验
一个简单判断方法是:
- 如果你的核心问题是"页面出来太慢",优先考虑
LCP - 如果你的核心问题是"点了没反应、交互卡顿",优先考虑
INP - 如果你的核心问题是"页面跳动、误触",优先考虑
CLS
第二步:固定实验室环境
在实验室中,必须先把这些条件固定住:
- 浏览器版本
- 设备画像
- 网络条件
- CPU 降速
- 页面入口
- 登录态与数据态
- 测试脚本与采样次数
输出至少包含:
- 主目标值
- 关键瀑布图或长任务信息
- 关键资源体积
- 失败日志
这里的作用不是替代线上数据,而是把实验反馈周期压缩到团队可持续迭代的程度。
第三步:用线上真实用户数据做最后确认
如果只靠实验室环境,你会遇到两个问题:
- 实验室很漂亮,线上收益一般
- 某些优化只对测试路径有效,对真实用户分布无效
所以每个准备晋级的实验都要经过线上真实用户数据确认,例如:
- 真实用户监测系统中相同页面、相同设备类型、相同国家或网络等级下的
p75指标 - 关键交互链路的真实用户延迟变化
- 错误率与埋点完整性是否受影响
简单理解:
- 实验室结果决定"值不值得继续看"
- 线上真实用户数据决定"值不值得保留到主线"
第四步:限定 agent 允许修改的范围
推荐按风险分层开放。关键不是"列一个大清单",而是把每层能碰什么、不能碰什么写清楚。
建议团队给 agent 一份明确边界:
- 允许修改哪些目录
- 允许修改哪些类型的代码
- 明确禁止改哪些模块
- 每轮只允许选一个假设
一个简单模板:
md
允许修改:
- src/routes/product/**
- src/components/hero/**
- src/utils/loaders/**
禁止修改:
- src/domain/order/**
- src/tracking/**
- src/seo/**
- server/**
允许的改动类型:
- 资源加载策略
- 代码切分
- 图片与字体加载策略
禁止的改动类型:
- 改业务逻辑
- 改埋点协议
- 改 SEO 结构
- 改后端接口
下面按三层说明。
第一层:低风险、容易比较
适合开放给 agent 的内容:
- 资源预加载与 preload 调整
- 路由与组件级代码切分
- 图片格式、尺寸与懒加载策略
- 字体加载策略
- 第三方脚本
defer/async/ 空闲时调度
适合放开的原因:
- 改动边界相对清楚
- 回滚容易
- 对业务逻辑影响较小
- 收益常常能在实验室里较快看到
代码示例 1:把第三方组件改成按需加载
tsx
import { lazy, Suspense } from "react";
const ReviewWidget = lazy(() => import("./ReviewWidget"));
export function ProductSidebar() {
return (
<aside>
<ProductSummary />
<Suspense fallback={null}>
<ReviewWidget />
</Suspense>
</aside>
);
}
代码示例 2:把首屏外图片改成原生懒加载
tsx
export function ProductGalleryImage({ src, alt }: { src: string; alt: string }) {
return (
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
width={640}
height={640}
/>
);
}
代码示例 3:把非关键第三方脚本延后到空闲阶段加载
ts
function loadScript(src: string) {
const script = document.createElement("script");
script.src = src;
script.async = true;
document.head.appendChild(script);
}
if ("requestIdleCallback" in window) {
window.requestIdleCallback(() => loadScript("https://example.com/chat-widget.js"));
} else {
window.setTimeout(() => loadScript("https://example.com/chat-widget.js"), 2000);
}
第二层:中风险、收益可能更大
适合开放给 agent 的内容:
- hydration 时机调整
- 关键渲染路径精简
- 列表与大块内容的延迟渲染
- 长任务拆分
- 事件处理中的调度与节流策略
这层收益更可能明显,但也更容易带来体验副作用,所以需要更严格的人工审查。
代码示例 1:把非关键区域延后挂载
tsx
import { useEffect, useState } from "react";
export function ProductPage() {
const [showRecommendations, setShowRecommendations] = useState(false);
useEffect(() => {
const timer = window.setTimeout(() => setShowRecommendations(true), 1200);
return () => window.clearTimeout(timer);
}, []);
return (
<>
<Hero />
<PriceBlock />
{showRecommendations ? <Recommendations /> : null}
</>
);
}
代码示例 2:把重计算拆到下一帧,减少一次长任务
ts
function onFilterChange(nextValue: string) {
setKeyword(nextValue);
requestAnimationFrame(() => {
runHeavyFilter(nextValue);
});
}
代码示例 3:对频繁触发的逻辑做节流
ts
function throttle<T extends (...args: any[]) => void>(fn: T, wait: number) {
let pending = false;
return (...args: Parameters<T>) => {
if (pending) return;
pending = true;
window.setTimeout(() => {
fn(...args);
pending = false;
}, wait);
};
}
const onScroll = throttle(() => {
updateVisibleCards();
}, 100);
window.addEventListener("scroll", onScroll, { passive: true });
第三层:高风险、需要人工强审
适合谨慎开放的内容:
- 页面结构大改
- 跨页面共享框架调整
- 核心状态管理改造
- SSR / CSR / islands 等架构切换
这层不建议一开始就开放给 agent,除非团队已经有稳定的验证链路和回滚机制。
代码示例 1:把整页从直接客户端渲染改成服务端下发关键内容
tsx
export async function ProductPageServer({ productId }: { productId: string }) {
const product = await getProduct(productId);
return (
<>
<Hero product={product} />
<PriceBlock product={product} />
<ClientRecommendations productId={productId} />
</>
);
}
代码示例 2:把全量客户端状态改成按页面拆分的局部状态
ts
import { create } from "zustand";
type ProductPageState = {
selectedSkuId: string | null;
setSelectedSkuId: (skuId: string) => void;
};
export const useProductPageStore = create<ProductPageState>((set) => ({
selectedSkuId: null,
setSelectedSkuId: (skuId) => set({ selectedSkuId: skuId }),
}));
代码示例 3:把页面的一部分改成 islands 式延后激活
tsx
export default function ProductPage() {
return (
<>
<StaticHero />
<StaticSpecs />
<InteractiveReviewsIsland client:visible />
</>
);
}
第五步:建立统一实验记录表
建议每轮实验都记到统一表里,例如 results.tsv 或团队共享表格,至少包含:
id:实验编号commitpage_or_flownorth_star_metricnorth_star_beforenorth_star_afterbottom_line_statuslab_resultfield_resultstatusdescriptionrisk_note
status 建议只有三种:
keepdiscardcrash
一个样例:
| id | commit | page_or_flow | north_star_metric | before | after | status | description |
|---|---|---|---|---|---|---|---|
| exp-01 | a1b2c3d | product-detail | p75 INP | 280ms | 280ms | keep | baseline |
| exp-02 | b2c3d4e | product-detail | p75 INP | 280ms | 235ms | keep | defer third-party review widget |
| exp-03 | c3d4e5f | product-detail | p75 INP | 235ms | 238ms | discard | aggressive image preload changed waterfall |
| exp-04 | d4e5f6g | product-detail | p75 INP | 235ms | invalid | crash | hydration split caused event binding failure |
第六步:定义保留 / 放弃 / 失败规则
推荐的默认规则:
keep
- 主目标明确改善
- 功能与体验底线未退化
- 代码维护成本在可接受范围内
- 线上真实用户数据没有明显反证
discard
- 主目标未改善
- 改善不稳定,复测后消失
- 虽有改善,但引入过高维护成本
- 伤害了底线指标
crash
- 页面功能失效
- 核心埋点失效
- 性能数据无法采集
- 改动导致明显运行时错误
重点不是把所有实验都做成 keep,而是快速而诚实地淘汰无效方案。
第七步:显式评估方案维护成本
建议团队在评审实验时,额外问四个问题:
- 这次收益是否足够大,值得团队长期维护?
- 如果 3 个月后回归,团队能快速定位吗?
- 这个方案是否严重依赖脆弱时序、魔法常量或灰色技巧?
- 有没有更简单但收益接近的替代方案?
如果答案偏负面,就算指标更好,也不一定要 keep。
第八步:安排实验节奏
推荐一个现实可行的节奏:
- 每次只跑 1 个优化假设
- 每个假设先经过实验室环境快速筛选
- 通过后进入小流量或线上观察
- 观察完成再决定 keep / discard
对多数团队来说,更适合的不是"让 agent 无限循环一整晚",而是:
- 每天固定 1 到 3 轮高质量实验
- 周度复盘实验记录表
- 每月沉淀高频有效模式
第九步:明确人和 agent 的分工
适合 agent 做的
- 读取既有性能报告
- 归纳瓶颈候选
- 生成单点实验假设
- 在限定范围内改代码
- 跑实验室测试并记录结果
- 对 keep / discard 给出建议
更适合人做的
- 选择北极星指标
- 定义底线指标
- 决定允许改动边界
- 判断维护成本是否过高
- 审核高风险改动是否值得晋级
这套方法的正确打开方式,不是让人退出系统,而是让人把判断力集中在真正需要判断的地方。
第十步:准备 agent prompt 模板
下面这份模板适合给 agent 作为单轮实验指令:
md
你现在负责一轮前端性能实验,只能在允许的改动范围内行动。
目标页面:
- 商品详情页移动端
北极星指标:
- p75 INP
底线指标:
- 关键功能不能回归
- 错误率不能上升
- CLS 不能变差
允许改动范围:
- 第三方脚本加载策略
- 代码切分
- 图片与字体加载策略
不允许改动范围:
- 核心业务逻辑
- 埋点协议
- SEO 结构
工作流程:
1. 先读取 baseline 和最近几轮实验记录
2. 只提出一个实验假设
3. 修改代码并记录改动说明
4. 跑实验室测试并记录主指标与底线指标
5. 给出 keep / discard / crash 建议
6. 如果收益不明确或风险偏高,默认 discard
维护原则:
- 同等收益下优先更简单的方案
- 小收益但明显增加维护成本,不保留
最后
这套方法真正的价值,不在于"用 AI 自动优化前端",而在于让性能优化从"靠高手临场发挥"变成"团队可重复运行的实验机制"。
先把实验环境、改动边界和结果记录搭好,再让 agent 开始优化。