---
name: web-vitals-rules
description: "Core Web Vitals 性能优化规范。适用于所有涉及前端页面渲染性能、资源加载及防布局抖动的代码编写。"
globs: ["src/**/*.tsx", "src/**/*.css", "src/**/*.html", "**/*.tsx", "**/*.jsx", "**/*.html"]
alwaysApply: false
updated: 2026-05-22
---

# Core Web Vitals 性能优化规范

> [!IMPORTANT]
> 卓越的页面性能是用户留存的决定性因素。AI 助手在编写前端页面布局或加载第三方静态资源时，必须确保避免累积布局偏移（CLS）并极大减少首屏加载时间。

## 1. 适用场景

当您开发新的页面组件、引入大体积第三方静态库、在 HTML/TSX 中加载多媒体资源（如图片、视频），或编写弹性盒流式布局时。

## 2. 操作规则

1. **零 CLS 布局占位 (Zero Cumulative Layout Shift)**：
   - 凡是包含图片（`<img>`）或动态异步加载数据的卡片，**必须在 CSS 中显式声明宽高比 (`aspect-ratio`) 或明确的宽高尺寸进行骨架占位**，防止资源加载完成后发生页面元素突兀的位移，确保 `CLS < 0.1`。
2. **图片与媒体加载优先级 (Media Loading Priority)**：
   - 首屏关键图片、商品主图、头像大图、Hero 图等 LCP 候选资源禁止使用 `loading="lazy"`；应使用框架图片组件、预加载或 `fetchpriority="high"` 等方式提高关键资源优先级。
   - 非首屏、列表下方、折叠区域或装饰性图片应声明 `loading="lazy"`，降低无关资源对首屏渲染的竞争。
3. **首屏加载包体压缩 (Code Splitting)**：
   - 针对非首屏路由或复杂的弹窗类重型组件（如富文本编辑器、三维图表、动画库），必须统一使用 React 的 `React.lazy()` 配合 `Suspense`（或 Next.js 的 `dynamic()`）进行动态按需拆包加载。

## 3. 禁止事项

- 严禁在页面没有任何宽度高度占位的情况下，直接渲染可能会突然插入页面头部的第三方异步广告或大体积图片卡片。
- 禁止将大体积的原始图片文件（超过 500KB）直接放入资产目录；图片资源应优先转化为现代高效的 WebP/AVIF 格式，并采用适当的 CDN 缩放。

## 4. 验证方式

- 运行 Chrome Lighthouse 进行 Core Web Vitals 跑分。

## 5. 代码对比示例

### ❌ 错误示例（导致严重的 CLS 布局严重抖动与高耗时 LCP）

```tsx
// 违反规则 2.1：<img> 标签裸写，无高宽比限制，加载出来的一瞬间会把下方的 <p> 标签野蛮顶开，引发 CLS 扣分
// 违反规则 2.2：没有区分首屏关键图和非首屏图片的加载优先级，首屏一口气请求所有图片
import React from 'react';

export const UnoptimizedList: React.FC = () => {
  return (
    <div>
      <h3>商品精选列表</h3>
      <div className="card">
        <img src="https://cdn.example.com/huge-banner.jpg" alt="商品图" /> 
        <p>这是优质好货推荐，超值价格抢购中！</p>
      </div>
    </div>
  );
};
```

###  正确方向（完美应用占位与懒加载，体验如丝般顺滑）

```tsx
import React, { Suspense } from "react";

// 遵循规则 2.3：重型图表组件使用 React.lazy 动态拆分打包按需加载
const HeavyAnalyticsChart = React.lazy(() => import("./HeavyAnalyticsChart"));

export const OptimizedProductPage: React.FC = () => {
  return (
    <div className="max-w-xl mx-auto p-4 space-y-6">
      <h3 className="text-xl font-bold text-gray-800">商品精选列表</h3>
      
      <div className="border rounded-lg overflow-hidden bg-white shadow-sm">
        {/* 
          遵循规则 2.1：通过 aspect-video (16/9) 或是显式 aspect-ratio 在图片加载完成前强行保留占位，彻底拦截布局抖动
          遵循规则 2.2：该图不是首屏 LCP 候选资源，添加 loading="lazy" 进行非阻塞异步加载
        */}
        <div className="w-full aspect-video bg-gray-200">
          <img 
            src="https://cdn.example.com/huge-banner.webp" 
            alt="商品图"
            loading="lazy"
            className="w-full h-full object-cover transition-opacity duration-300"
          />
        </div>
        <div className="p-4">
          <p className="text-gray-600">这是优质好货推荐，超值价格抢购中！</p>
        </div>
      </div>

      {/* 遵循规则 2.3：动态包体降级优雅展示 */}
      <Suspense fallback={<div className="h-64 bg-gray-50 flex items-center justify-center">正在加载核心图表数据...</div>}>
        <HeavyAnalyticsChart />
      </Suspense>
    </div>
  );
};
```
