阅读视图

发现新文章,点击刷新页面。

前端性能优化实战指南

概述

性能优化是前端开发中至关重要的一环。优秀的性能不仅提升用户体验,还能提高转化率、降低跳出率,并改善 SEO 排名。本文将深入探讨前端性能优化的核心策略和实战技巧。

一、性能指标与测量

1.1 核心 Web 指标 (Core Web Vitals)

// 使用 Web Vitals 库测量核心指标
import { getCLS, getFID, getLCP } from 'web-vitals';

getCLS(console.log);   // 累积布局偏移
getFID(console.log);   // 首次输入延迟
getLCP(console.log);   // 最大内容绘制

关键指标说明:

  • LCP (Largest Contentful Paint) : 最大内容绘制,衡量加载性能

    • 优秀:≤ 2.5 秒
    • 需要改进:2.5-4.0 秒
    • 差:> 4.0 秒
  • FID (First Input Delay) : 首次输入延迟,衡量交互性

    • 优秀:≤ 100 毫秒
    • 需要改进:100-300 毫秒
    • 差:> 300 毫秒
  • CLS (Cumulative Layout Shift) : 累积布局偏移,衡量视觉稳定性

    • 优秀:≤ 0.1
    • 需要改进:0.1-0.25
    • 差:> 0.25

1.2 性能测量工具

# 使用 Lighthouse 进行性能审计
npx lighthouse https://example.com --view

# 使用 Chrome DevTools Performance 面板
# 使用 WebPageTest 进行多地点测试

# 使用 PageSpeed Insights
curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://example.com"

二、加载性能优化

2.1 资源压缩与优化

2.1.1 图片优化

// 使用 modern 图片格式
<img src="image.webp" alt="描述" 
     srcset="image-400w.webp 400w, image-800w.webp 800w"
     sizes="(max-width: 600px) 400px, 800px"
     loading="lazy">

// 使用 picture 元素提供多种格式
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="描述" loading="lazy">
</picture>

图片优化策略:

  • 使用 WebP/AVIF 等现代格式
  • 实现响应式图片(srcset + sizes)
  • 懒加载非首屏图片
  • 使用 CDN 进行图片优化

2.1.2 代码压缩

// Vite 配置优化
export default defineConfig({
  build: {
    minify: 'terser', // 使用 terser 进行压缩
    terserOptions: {
      compress: {
        drop_console: true, // 生产环境移除 console
        drop_debugger: true,
      },
    },
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
          utils: ['lodash-es', 'dayjs'],
        },
      },
    },
  },
});

2.2 资源预加载与预获取

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

<!-- 未来导航预获取 -->
<link rel="prefetch" href="/js/about.js">
<link rel="preconnect" href="https://api.example.com">

<!-- 智能预加载 -->
<script>
  // 检测用户意图,预加载可能访问的页面
  document.addEventListener('mouseover', (e) => {
    if (e.target.tagName === 'A') {
      const url = e.target.href;
      if (isSameOrigin(url)) {
        const link = document.createElement('link');
        link.rel = 'prefetch';
        link.href = url;
        document.head.appendChild(link);
      }
    }
  });
</script>

2.3 代码分割与懒加载

// 路由级代码分割
const routes = [
  {
    path: '/',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    component: () => import('@/views/About.vue')
  },
  {
    path: '/admin',
    component: () => import('@/views/Admin.vue'),
    meta: { requiresAuth: true }
  }
];

// 组件级懒加载
const HeavyChart = defineAsyncComponent({
  loader: () => import('@/components/HeavyChart.vue'),
  loadingComponent: LoadingSpinner,
  delay: 200,
  timeout: 3000
});

// 按需加载第三方库
const loadLodash = async () => {
  const _ = await import('lodash-es');
  return _.default;
};

三、运行时性能优化

3.1 渲染优化

3.1.1 虚拟列表

<!-- 实现虚拟列表处理大量数据 -->
<template>
  <div class="virtual-list" ref="listContainer">
    <div :style="{ height: totalHeight + 'px' }">
      <div 
        v-for="item in visibleItems" 
        :key="item.id"
        :style="{ 
          transform: `translateY(${item.offset}px)`,
          position: 'absolute',
          top: 0,
          left: 0,
          right: 0
        }"
      >
        <ItemComponent :item="item" />
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';

const props = defineProps({
  items: { type: Array, required: true },
  itemHeight: { type: Number, default: 50 }
});

const listContainer = ref(null);
const scrollTop = ref(0);

const visibleCount = 20;
const totalHeight = computed(() => props.items.length * props.itemHeight);

const visibleItems = computed(() => {
  const start = Math.floor(scrollTop.value / props.itemHeight);
  const end = Math.min(start + visibleCount, props.items.length);
  return props.items
    .slice(start, end)
    .map((item, index) => ({
      ...item,
      offset: (start + index) * props.itemHeight
    }));
});

const handleScroll = () => {
  scrollTop.value = listContainer.value.scrollTop;
};

onMounted(() => {
  listContainer.value.addEventListener('scroll', handleScroll);
});

onUnmounted(() => {
  listContainer.value.removeEventListener('scroll', handleScroll);
});
</script>

3.1.2 防抖与节流

// 防抖函数
function debounce(func, wait, immediate = false) {
  let timeout;
  return function(...args) {
    const later = () => {
      timeout = null;
      if (!immediate) func.apply(this, args);
    };
    const callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(this, args);
  };
}

// 节流函数
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 使用示例
const handleScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY);
}, 100);

const handleResize = debounce(() => {
  console.log('Window resized');
  updateLayout();
}, 300);

3.2 内存优化

// 避免内存泄漏
class DataFetcher {
  constructor() {
    this.abortController = new AbortController();
    this.cache = new Map();
  }

  async fetchData(url) {
    try {
      const response = await fetch(url, {
        signal: this.abortController.signal
      });
      const data = await response.json();
      this.cache.set(url, data);
      return data;
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('Request aborted');
      } else {
        throw error;
      }
    }
  }

  destroy() {
    this.abortController.abort();
    this.cache.clear();
  }
}

// 使用 WeakMap 避免内存泄漏
const componentData = new WeakMap();

function registerComponent(component, data) {
  componentData.set(component, data);
  // 当 component 被垃圾回收时,data 也会自动释放
}

3.3 Web Worker 优化

// 主线程
const worker = new Worker('./worker.js');

worker.postMessage({
  type: 'PROCESS_DATA',
  data: largeDataSet
});

worker.onmessage = (e) => {
  const result = e.data;
  updateUI(result);
};

// worker.js
self.onmessage = (e) => {
  const { type, data } = e.data;
  
  if (type === 'PROCESS_DATA') {
    const result = heavyComputation(data);
    self.postMessage(result);
  }
};

function heavyComputation(data) {
  // 繁重的计算逻辑
  return data.map(item => item * 2).filter(x => x > 10);
}

四、网络优化

4.1 HTTP/2与HTTP/3

# Nginx HTTP/2 配置
server {
    listen 443 ssl http2;
    server_name example.com;
    
    # HTTP/2 推送
    http2_push /js/app.js;
    http2_push /css/style.css;
    
    # 多路复用优化
    tcp_nodelay on;
    tcp_nopush on;
}

4.2 缓存策略

// Service Worker 缓存策略
const CACHE_NAME = 'v1';
const CACHE_STRATEGIES = {
  // 缓存优先
  static: ['/', '/index.html', '/css/*', '/js/*'],
  
  // 网络优先
  api: '/api/*',
  
  // 过期时间
  images: {
    pattern: '/images/*',
    maxAge: 7 * 24 * 60 * 60 // 7 天
  }
};

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  
  if (CACHE_STRATEGIES.static.some(pattern => url.pathname.includes(pattern))) {
    event.respondWith(cachedFirst(event.request));
  } else if (url.pathname.startsWith('/api/')) {
    event.respondWith(networkFirst(event.request));
  } else {
    event.respondWith(staleWhileRevalidate(event.request));
  }
});

async function cachedFirst(request) {
  const cached = await caches.match(request);
  if (cached) return cached;
  
  const response = await fetch(request);
  if (response.ok) {
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
  }
  return response;
}

4.3 请求优化

// 请求合并
class RequestBatcher {
  constructor(batchSize = 10, batchDelay = 100) {
    this.batchSize = batchSize;
    this.batchDelay = batchDelay;
    this.queue = [];
    this.timer = null;
  }

  add(request) {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, resolve, reject });
      this.flush();
    });
  }

  flush() {
    if (this.timer) clearTimeout(this.timer);
    
    if (this.queue.length >= this.batchSize) {
      this.executeBatch();
    } else {
      this.timer = setTimeout(() => this.executeBatch(), this.batchDelay);
    }
  }

  async executeBatch() {
    if (this.queue.length === 0) return;
    
    const batch = [...this.queue];
    this.queue = [];
    
    try {
      const responses = await Promise.all(batch.map(item => item.request));
      batch.forEach((item, index) => item.resolve(responses[index]));
    } catch (error) {
      batch.forEach(item => item.reject(error));
    }
  }
}

// 使用示例
const batcher = new RequestBatcher();

// 批量请求
const results = await Promise.all([
  batcher.add(fetch('/api/user/1')),
  batcher.add(fetch('/api/user/2')),
  batcher.add(fetch('/api/user/3'))
]);

五、构建优化

5.1 依赖分析

# 分析打包体积
npx webpack-bundle-analyzer dist/stats.json

# 使用 source-map-explorer
npx source-map-explorer dist/js/*.js

# Vite 内置分析
vite build --analyze
// webpack 配置
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\/]node_modules[\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        default: {
          minChunks: 2,
          priority: -10,
          reuseExistingChunk: true,
        },
      },
    },
  },
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
    }),
  ],
};

5.2 Tree Shaking

// 确保使用 ES 模块语法
import { debounce, throttle } from 'lodash-es'; // ✅ 支持 tree shaking
// import _ from 'lodash'; // ❌ 会引入整个库

// 使用 sideEffects 配置
// package.json
{
  "sideEffects": [
    "*.css",
    "*.scss"
  ]
}

// 标记纯函数
/*#__PURE__*/
function pureFunction() {
  return 42;
}

六、监控与分析

6.1 性能监控

// 自定义性能监控
class PerformanceMonitor {
  constructor() {
    this.metrics = {};
    this.init();
  }

  init() {
    // 监听核心 Web 指标
    if ('PerformanceObserver' in window) {
      this.observeLCP();
      this.observeCLS();
      this.observeFID();
    }

    // 监听页面加载性能
    window.addEventListener('load', () => {
      this.recordLoadPerformance();
    });
  }

  observeLCP() {
    new PerformanceObserver((list) => {
      const entries = list.getEntries();
      const lastEntry = entries[entries.length - 1];
      this.recordMetric('LCP', lastEntry.startTime);
    }).observe({ type: 'largest-contentful-paint', buffered: true });
  }

  observeCLS() {
    let clsValue = 0;
    new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (!entry.hadRecentInput) {
          clsValue += entry.value;
        }
      }
      this.recordMetric('CLS', clsValue);
    }).observe({ type: 'layout-shift', buffered: true });
  }

  recordMetric(name, value) {
    this.metrics[name] = value;
    
    // 发送到分析服务
    this.sendToAnalytics(name, value);
  }

  recordLoadPerformance() {
    const timing = performance.timing;
    const loadTime = timing.loadEventEnd - timing.navigationStart;
    this.recordMetric('LoadTime', loadTime);
  }

  sendToAnalytics(name, value) {
    // 发送到监控服务
    navigator.sendBeacon('/api/performance', 
      JSON.stringify({ metric: name, value, timestamp: Date.now() })
    );
  }

  getReport() {
    return {
      ...this.metrics,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
      url: window.location.href
    };
  }
}

// 使用
const monitor = new PerformanceMonitor();

6.2 错误监控

// 全局错误处理
window.addEventListener('error', (event) => {
  reportError({
    type: 'javascript',
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    error: event.error
  });
});

window.addEventListener('unhandledrejection', (event) => {
  reportError({
    type: 'promise',
    message: event.reason?.message || 'Unhandled promise rejection',
    error: event.reason
  });
});

function reportError(error) {
  // 发送到错误监控服务
  navigator.sendBeacon('/api/error', JSON.stringify({
    ...error,
    timestamp: Date.now(),
    url: window.location.href,
    userAgent: navigator.userAgent
  }));
  
  // 可选:记录到控制台
  console.error('Performance Error:', error);
}

七、实战案例

7.1 电商网站性能优化

优化前:

  • LCP: 4.2s
  • FID: 350ms
  • CLS: 0.35
  • 首屏加载时间:5.1s

优化措施:

  1. 图片优化(WebP + 懒加载)
  2. 代码分割(路由级 + 组件级)
  3. 预加载关键资源
  4. Service Worker 缓存
  5. HTTP/2 启用

优化后:

  • LCP: 1.8s ✅
  • FID: 85ms ✅
  • CLS: 0.08 ✅
  • 首屏加载时间:2.1s ✅

总结

前端性能优化是一个持续的过程,需要:

核心策略:

  1. 测量先行 - 使用工具了解当前性能状况
  2. 渐进优化 - 从影响最大的地方开始
  3. 持续监控 - 建立性能监控体系
  4. 团队协作 - 将性能纳入开发流程

关键要点:

  • 图片优化通常带来最大收益
  • 代码分割能显著改善首屏加载
  • 缓存策略对重复访问至关重要
  • 运行时优化提升用户体验
  • 监控确保优化效果持续

记住:性能优化不是一次性的任务,而是持续改进的过程。定期测量、分析、优化,确保你的应用始终保持最佳性能。

SEO 优化

引言

在 Web 开发中,SEO(搜索引擎优化)是提升网站可见性和流量的关键因素。即使拥有再精美的界面和流畅的交互,如果搜索引擎无法有效抓取和索引你的内容,潜在用户就很难发现你的网站。本文将深入探讨前端 SEO 优化的核心策略,包括服务端渲染(SSR)、meta 标签优化和 sitemap 配置。

一、服务端渲染(SSR)

为什么 SSR 对 SEO 至关重要

传统的客户端渲染(CSR)应用,如使用 React、Vue 构建的单页应用,在初始加载时往往只返回一个空的 HTML 文件,内容通过 JavaScript 动态生成。这对搜索引擎爬虫来说是个挑战,因为:

  1. 爬虫可能无法执行 JavaScript
  2. 即使能执行,加载和渲染过程也会增加爬取成本
  3. 内容加载延迟可能导致索引不完整

SSR 通过在服务器上生成完整的 HTML 页面,确保爬虫能够立即看到完整内容。

Next.js SSR 实现示例

// pages/index.js
import { useRouter } from 'next/router';

export async function getServerSideProps(context) {
  // 在服务器端获取数据
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();
  
  return {
    props: {
      products
    }
  };
}

export default function Home({ products }) {
  return (
    <div>
      <h1>产品列表</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            <h2>{product.name}</h2>
            <p>{product.description}</p>
            <span>${product.price}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

SSR vs SSG 选择建议

场景 推荐方案 理由
内容频繁更新 SSR 实时获取最新数据
内容相对稳定 SSG 更好的性能
个性化内容 SSR 每用户不同内容
博客/文档 SSG + ISR 平衡性能与更新

二、Meta 标签优化

基础 Meta 标签

每个页面都应该包含以下基础 meta 标签:

<head>
  <!-- 字符编码 -->
  <meta charset="UTF-8">
  
  <!-- 视口设置 -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <!-- 页面描述 -->
  <meta name="description" content="这里是页面的详细描述,150-160 个字符最佳">
  
  <!-- 关键词(现代搜索引擎已不太重视) -->
  <meta name="keywords" content="关键词 1, 关键词 2, 关键词 3">
  
  <!-- 作者 -->
  <meta name="author" content="作者名称">
  
  <!-- 机器人指令 -->
  <meta name="robots" content="index, follow">
</head>

Open Graph 社交分享优化

<!-- Facebook / LinkedIn -->
<meta property="og:title" content="页面标题">
<meta property="og:description" content="页面描述">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:url" content="https://example.com/page">
<meta property="og:type" content="website">

<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="页面标题">
<meta name="twitter:description" content="页面描述">
<meta name="twitter:image" content="https://example.com/image.jpg">

Next.js 中动态 Meta 标签

// components/SEO.js
import Head from 'next/head';

export default function SEO({ title, description, image, url }) {
  return (
    <Head>
      <title>{title}</title>
      <meta name="description" content={description} />
      
      {/* Open Graph */}
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={image} />
      <meta property="og:url" content={url} />
      <meta property="og:type" content="website" />
      
      {/* Twitter */}
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:title" content={title} />
      <meta name="twitter:description" content={description} />
      <meta name="twitter:image" content={image} />
      
      {/* Canonical URL */}
      <link rel="canonical" href={url} />
    </Head>
  );
}

三、Sitemap 配置

什么是 Sitemap

Sitemap(站点地图)是一个 XML 文件,列出了网站的所有重要页面,帮助搜索引擎了解网站结构并更有效地爬取。

生成 Sitemap

// utils/generateSitemap.js
const fs = require('fs');
const sitemap = require('sitemap');

const routes = [
  '',
  '/about',
  '/products',
  '/blog',
  '/contact'
];

// 获取动态路由
async function getDynamicRoutes() {
  const products = await fetchProducts();
  return products.map(p => `/products/${p.id}`);
}

function generateSitemap() {
  const sm = sitemap.createSitemap({
    hostname: 'https://example.com',
    cacheTime: 600000 // 10 分钟缓存
  });

  // 添加静态路由
  routes.forEach(route => {
    sm.add({
      url: route,
      lastmod: Date.now(),
      changefreq: 'weekly',
      priority: 0.8
    });
  });

  // 生成 XML
  const sitemapXML = sm.toString();
  
  // 写入文件
  fs.writeFileSync('public/sitemap.xml', sitemapXML);
}

generateSitemap();

Next.js 动态 Sitemap

// pages/sitemap.xml.js
export async function getServerSideProps({ res }) {
  const baseUrl = 'https://example.com';
  
  // 获取所有产品
  const products = await fetchProducts();
  
  // 构建 sitemap
  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>${baseUrl}/</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  ${products.map(product => `
  <url>
    <loc>${baseUrl}/products/${product.id}</loc>
    <lastmod>${product.updatedAt}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>
  `).join('')}
</urlset>`;

  res.setHeader('Content-Type', 'text/xml');
  res.write(sitemap);
  res.end();
  
  return { props: {} };
}

提交 Sitemap 到搜索引擎

  1. Google Search Console: 登录 → 索引 → Sitemap → 输入 sitemap.xml 路径
  2. Bing Webmaster Tools: 提交站点地图
  3. robots.txt 引用:
# robots.txt
User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml

四、其他 SEO 最佳实践

结构化数据(Schema.org)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "产品名称",
  "description": "产品描述",
  "image": "https://example.com/product.jpg",
  "offers": {
    "@type": "Offer",
    "price": "99.99",
    "priceCurrency": "CNY",
    "availability": "https://schema.org/InStock"
  }
}
</script>

URL 优化

  • 使用描述性、包含关键词的 URL
  • 保持 URL 简短
  • 使用连字符分隔单词
  • 避免特殊字符和参数
✅ 好的 URL:
https://example.com/blog/how-to-optimize-seo

❌ 差的 URL:
https://example.com/p=123
https://example.com/blog.php?id=456&cat=7

内部链接策略

  • 建立清晰的导航结构
  • 使用描述性的锚文本
  • 确保重要页面在 3 次点击内可达
  • 添加面包屑导航

总结

SEO 优化是一个持续的过程,需要技术实现和内容策略的结合。关键要点:

  1. SSR /SSG:确保搜索引擎能立即看到完整内容
  2. Meta 标签:每个页面都要有独特的标题和描述
  3. Sitemap:帮助搜索引擎发现所有重要页面
  4. 结构化数据:增强搜索结果展示
  5. 持续监控:使用 Google Search Console 等工具跟踪效果

记住,SEO 不是一次性的工作,而是需要持续优化的过程。定期审查你的 SEO 策略,根据搜索引擎算法的变化和数据分析结果进行调整。

CDN 与缓存策略

引言

在现代前端开发中,性能优化是提升用户体验的关键环节。CDN(内容分发网络)和合理的缓存策略,能够显著减少资源加载时间,降低服务器压力,是前端性能优化的两大核心手段。本文将深入探讨 CDN 的工作原理、缓存策略的设计要点,以及如何在实际项目中应用这些技术。

一、CDN 基础原理

什么是 CDN

CDN(Content Delivery Network)是一种分布式的网络架构,通过将静态资源缓存到全球各地的边缘节点,让用户从距离最近的节点获取资源,从而降低延迟、提升访问速度。

CDN 工作流程

用户请求 → DNS 解析 → 选择最优节点 → 返回资源
              ↓
         节点有缓存?→ 直接返回
              ↓
         回源站获取 → 缓存到节点 → 返回给用户

常见 CDN 服务商

  • 国内:阿里云 CDN、腾讯云 CDN、网宿科技
  • 国际:Cloudflare、AWS CloudFront、Akamai

二、缓存策略核心概念

1. 浏览器缓存机制

浏览器缓存主要通过 HTTP 响应头来控制:

# 强缓存
Cache-Control: max-age=31536000, public
Expires: Wed, 21 Apr 2027 07:00:00 GMT

# 协商缓存
ETag: "33a64df551425fcc55e4d42a1459b3"
Last-Modified: Wed, 21 Apr 2026 07:00:00 GMT

2. Cache-Control 详解

Cache-Control 是最常用的缓存控制头,支持多个指令:

// 常见指令说明
max-age=31536000    // 缓存 1 年(秒)
public              // 可被任何缓存存储
private             // 仅用户浏览器可缓存
no-cache            // 使用前需验证
no-store            // 不缓存任何内容
must-revalidate     // 过期后必须验证

3. 强缓存 vs 协商缓存

类型 特点 适用场景
强缓存 直接从本地读取,不请求服务器 静态资源(JS、CSS、图片)
协商缓存 向服务器验证是否过期 动态内容、HTML 页面

三、实际代码示例

1. Nginx 缓存配置

server {
    listen 80;
    server_name example.com;
    
    # 静态资源 - 强缓存 1 年
    location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        # 添加版本号到文件名
        rewrite ^/(.*).[0-9a-f]{8}.(.*)$ /$1.$2 last;
    }
    
    # HTML 文件 - 协商缓存
    location ~* .html$ {
        expires -1;
        add_header Cache-Control "no-cache, must-revalidate";
    }
    
    # API 接口 - 不缓存
    location /api/ {
        add_header Cache-Control "no-store, no-cache, must-revalidate";
    }
}

2. 资源版本管理

通过文件名哈希实现长期缓存:

// Webpack 配置
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js'
  }
};

// Vite 配置
export default {
  build: {
    rollupOptions: {
      output: {
        entryFileNames: `[name].[hash].js`,
        chunkFileNames: `[name].[hash].js`,
        assetFileNames: `[name].[hash].[ext]`
      }
    }
  }
};

3. Service Worker 缓存

// sw.js
const CACHE_NAME = 'v1';
const urlsToCache = [
  '/',
  '/static/js/main.js',
  '/static/css/main.css',
  '/images/logo.png'
];

// 安装:缓存资源
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

// 拦截请求:优先从缓存读取
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        if (response) {
          return response; // 返回缓存
        }
        return fetch(event.request)
          .then(response => {
            // 克隆响应并缓存
            const responseClone = response.clone();
            caches.open(CACHE_NAME)
              .then(cache => cache.put(event.request, responseClone));
            return response;
          });
      });
  );
});

四、CDN 最佳实践

1. 资源分类策略

// 资源分类缓存策略
const cacheStrategies = {
  // 核心资源 - 短缓存
  'index.html': { maxAge: 0, mustRevalidate: true },
  
  // 静态资源 - 长缓存 + 版本控制
  'js/*.js': { maxAge: 31536000, immutable: true },
  'css/*.css': { maxAge: 31536000, immutable: true },
  'images/*': { maxAge: 31536000, immutable: true },
  
  // API 数据 - 不缓存或短缓存
  'api/*': { maxAge: 0, noStore: true }
};

2. 缓存失效策略

方案一:文件名哈希(推荐)

<!-- 文件名包含哈希,内容变化时哈希改变 -->
<script src="/js/main.a1b2c3d4.js"></script>
<link rel="stylesheet" href="/css/style.e5f6g7h8.css">

方案二:查询参数

<script src="/js/main.js?v=20260422"></script>

方案三: CDN 刷新

# 阿里云 CDN 刷新接口
curl -X POST "https://cdn.aliyuncs.com/?Action=RefreshCdnObject&ObjectPath=https://example.com/js/main.js"

3. 性能优化技巧

// 预加载关键资源
<link rel="preload" href="/fonts/main.woff2" as="font">
<link rel="preload" href="/js/vendor.js" as="script">

// 预获取后续可能需要的资源
<link rel="prefetch" href="/js/page2.js">

// 资源提示
<link rel="dns-prefetch" href="https://api.example.com">
<link rel="preconnect" href="https://cdn.example.com">

五、监控与优化

1. 性能指标监控

// 使用 Performance API 监控资源加载
window.addEventListener('load', () => {
  const entries = performance.getEntriesByType('resource');
  entries.forEach(entry => {
    console.log(`${entry.name}: ${entry.duration}ms`);
  });
  
  // 计算资源加载时间
  const loadTime = performance.timing.loadEventEnd - 
                   performance.timing.navigationStart;
  console.log(`Total load time: ${loadTime}ms`);
});

2. 缓存命中率统计

// 通过响应头判断缓存状态
fetch('/api/data')
  .then(response => {
    const cacheStatus = response.headers.get('X-Cache');
    console.log(`Cache status: ${cacheStatus}`); // HIT or MISS
  });

总结

CDN 和缓存策略是前端性能优化的基石。通过合理配置:

  1. 静态资源使用强缓存 + 文件名哈希
  2. 动态内容使用协商缓存
  3. 关键资源使用预加载
  4. 持续监控缓存命中率和加载性能

记住:好的缓存策略 = 正确的 Cache-Control + 合理的资源版本管理 + 完善的失效机制。

❌