Back to Blog
By AriesZhou · · 6 min read

Frontend Performance Optimization Notes

Performance Optimization

Research shows that for every additional second of page load time, user churn can rise by 7%.

1. Core metrics: Web Vitals

Google’s Web Vitals are the core metrics for measuring user experience:

1.1 LCP (Largest Contentful Paint)

Largest Contentful Paint, measures how long it takes for the main content of a page to finish loading.

// 使用 Web Vitals 库监控 LCP
import { onLCP } from 'web-vitals';

onLCP(({ value, entries }) => {
  console.log(`LCP: ${value}ms`);
  // 优化目标: LCP < 2.5s
});

1.2 FID (First Input Delay)

First Input Delay, measures how quickly the page responds to the user’s first interaction.

import { onFID } from 'web-vitals';

onFID(({ value }) => {
  console.log(`FID: ${value}ms`);
  // 优化目标: FID < 100ms
});

1.3 CLS (Cumulative Layout Shift)

Cumulative Layout Shift, measures the visual stability of a page.

import { onCLS } from 'web-vitals';

onCLS(({ value }) => {
  console.log(`CLS: ${value}`);
  // 优化目标: CLS < 0.1
});

p s: You can test these metrics in one click using the Lighthouse panel in Chrome DevTools.

2. Resource optimization strategies

2.1 Resource compression and bundling

// webpack 配置示例
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
          },
        },
      }),
    ],
  },
};

2.2 Image optimization

// 使用 Next.js Image 组件
import Image from 'next/image';

function ProductImage({ src, alt }) {
  return (
    <Image
      src={src}
      alt={alt}
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      placeholder="blur"
      blurDataURL="data:image/..." // 模糊占位图
      loading="lazy"
      quality={75}
      width={400}
      height={300}
    />
  );
}

2.3 WebP image format

<!-- 响应式图片使用 picture 标签 -->
<picture>
  <source srcset="image.webp" type="image/webp" />
  <source srcset="image.jpg" type="image/jpeg" />
  <img src="image.jpg" alt="描述" />
</picture>

3. Caching strategies

3.1 Browser cache configuration

// Next.js next.config.js 配置
module.exports = {
  async headers() {
    return [
      {
        source: '/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        source: '/:path*.json',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=3600, must-revalidate',
          },
        ],
      },
    ];
  },
};

3.2 Service Worker caching

// sw.js
const CACHE_NAME = 'my-app-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/main.js',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(urlsToCache);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // 缓存命中返回缓存,否则请求网络
      return response || fetch(event.request);
    })
  );
});

4. Code splitting and lazy loading

4.1 Route-level code splitting

// React Router v6 + React.lazy
import { Routes, Route } from 'react-router-dom';
import { Suspense, lazy } from 'react';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

4.2 Component-level lazy loading

import { useState, lazy, Suspense } from 'react';

function ModalContainer() {
  const [showModal, setShowModal] = useState(false);

  // 动态导入大型组件
  const HeavyModal = lazy(() => import('./HeavyModal'));

  return (
    <>
      <button onClick={() => setShowModal(true)}>打开弹窗</button>
      {showModal && (
        <Suspense fallback={<ModalLoading />}>
          <HeavyModal onClose={() => setShowModal(false)} />
        </Suspense>
      )}
    </>
  );
}

4.3 Image lazy loading

// 使用 Intersection Observer 实现图片懒加载
function LazyImage({ src, alt }) {
  const [isVisible, setIsVisible] = useState(false);
  const imgRef = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      });
    });

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <div ref={imgRef}>
      {isVisible && <img src={src} alt={alt} />}
    </div>
  );
}

5. Rendering optimization

5.1 Reducing reflow and repaint

// ❌ 避免:多次修改 DOM 触发多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';

// ✅ 推荐:使用 CSS 类一次性修改
element.classList.add('expanded');

// ✅ 推荐:使用 document fragment
const fragment = document.createDocumentFragment();
items.forEach(item => {
  const li = document.createElement('li');
  li.textContent = item;
  fragment.appendChild(li);
});
ul.appendChild(fragment);

5.2 Using CSS Transform and Opacity

/* ❌ 影响布局的属性 */
.element {
  width: 100px;
  height: 100px;
  top: 50px;
}

/* ✅ 不影响布局的属性 */
.element {
  transform: translateX(50px);
  opacity: 0.5;
}

5.3 React Performance Optimization

import { memo, useMemo, useCallback } from 'react';

// 使用 memo 避免不必要的重渲染
const ListItem = memo(({ item, onClick }) => {
  return <li onClick={() => onClick(item.id)}>{item.name}</li>;
});

// 使用 useMemo 缓存计算结果
function ExpensiveComponent({ data, filter }) {
  const filteredData = useMemo(() => {
    return data.filter(item => item.name.includes(filter));
  }, [data, filter]);

  return filteredData.map(item => <ListItem key={item.id} item={item} />);
}

// 使用 useCallback 缓存回调函数
function Parent() {
  const handleClick = useCallback((id) => {
    console.log('Clicked:', id);
  }, []);

  return <Child onClick={handleClick} />;
}

6. Network Optimization

6.1 CDN Configuration

// next.config.js 配置 CDN
module.exports = {
  images: {
    domains: ['cdn.example.com', 'images.unsplash.com'],
    path: '/_next/image',
    loader: 'default',
  },
};

6.2 Preconnecting and Preloading

<!-- 预连接到关键域名 -->
<link rel="preconnect" href="https://cdn.example.com" />

<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin />

<!-- 预取下一个路由 -->
<link rel="prefetch" href="/dashboard" />

6.3 Loading Third-Party Scripts Asynchronously

<!-- 方式 1: async -->
<script src="https://analytics.example.com/script.js" async></script>

<!-- 方式 2: defer -->
<script src="https://analytics.example.com/script.js" defer></script>

<!-- 方式 3: 动态加载 -->
<script>
  const script = document.createElement('script');
  script.src = 'https://analytics.example.com/script.js';
  script.async = true;
  document.head.appendChild(script);
</script>

7. Performance Testing Tools

7.1 Lighthouse

# 使用 Chrome CLI 运行 Lighthouse
lighthouse https://example.com \
  --preset=desktop \
  --view \
  --output=json \
  --output-path=./lighthouse-report.json

7.2 Web Vitals Field Metrics

// 完整的性能监控实现
import { getCLS, getFID, getLCP } from 'web-vitals';

function sendToAnalytics({ name, value, id }) {
  // 发送到分析服务
  gtag('event', name, {
    event_category: 'Web Vitals',
    event_label: id,
    value: Math.round(name === 'CLS' ? value * 1000 : value),
  });
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);

8. Common Optimization Patterns

graph TD
    A[用户请求] --> B{缓存检查}
    B -->|命中| C[返回缓存资源]
    B -->|未命中| D[请求服务器]
    D --> E{资源类型}
    E -->|静态资源| F[CDN 响应]
    E -->|动态内容| G[服务器处理]
    F --> H[存入浏览器缓存]
    G --> I[生成响应]
    H --> C
    I --> C

9. Optimization Checklist

Optimization ItemTargetPriority
LCP< 2.5sHigh
FID< 100msHigh
CLS< 0.1High
Image CompressionWebP + ResponsiveHigh
Code SplittingSplit by RouteHigh
Caching StrategyStatic Assets for One YearMedium
Lazy LoadingNon-Critical ResourcesMedium
CDNGlobal NodesLow

10. Summary

Frontend performance optimization is a systematic effort that requires addressing multiple dimensions:

  • Metric-driven: Use Web Vitals as the core metrics
  • User experience: Focus on actual loading and interaction experience
  • Progressive enhancement: Prioritize optimizing the critical rendering path
  • Continuous monitoring: Establish a performance monitoring system

Note: Optimization should be targeted; over-optimization increases maintenance costs. Use Lighthouse for regular checks and focus on the issues that impact users the most.