⚡ 我的博客性能优化实战:首屏加载从2.5s降到1.2s
分享我在Vue 3博客项目中的性能优化经验,性能提升52%,包体积减少39%
前言
性能优化是前端开发中永恒的话题。在开发我的个人博客项目时,我遇到了首屏加载慢、包体积大等问题。经过一系列优化,最终将首屏加载时间从2.5s降到1.2s,包体积从850KB降到520KB。本文将详细分享我的优化过程和经验。
优化前后对比
先来看看优化前后的数据对比:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 首屏加载时间 | ~2.5s | ~1.2s | ⬇️ 52% |
| 初始包体积 | ~850KB | ~520KB | ⬇️ 39% |
| 冷启动时间 | ~3.5s | ~1.8s | ⬇️ 49% |
| Lighthouse性能评分 | 68 | 92 | ⬆️ 35% |
问题分析
1. 首屏加载慢
问题表现:
- 打开首页需要等待2.5秒才能看到内容
- 白屏时间长,用户体验差
- 移动端加载更慢
原因分析:
bash
# 使用vite-plugin-visualizer分析构建结果
npm run analyze
分析发现:
- vendor chunk过大,包含了所有依赖
- Element Plus图标全部加载
- 没有使用懒加载,所有页面代码一次性加载
- 图片没有压缩和懒加载
2. 包体积大
问题表现:
- 打包后的dist目录达到3MB
- vendor.js文件850KB
- 首屏需要加载的资源太多
原因分析:
- 所有依赖都打包到一个vendor chunk
- 没有使用代码分割
- 生产环境仍然包含console.log
- 没有压缩图片
3. 冷启动慢
问题表现:
- 开发服务器启动需要3.5秒
- HMR更新慢
原因分析:
- Vite配置没有优化
- 依赖没有预构建
- 文件监听配置不当
优化方案
1. 代码分割
代码分割是性能优化的核心,它可以将代码拆分成多个小块,按需加载。
按路由分割
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
manualChunks: {
// 将Vue相关依赖打包
'vue-vendor': ['vue', 'vue-router', 'pinia'],
// 将UI库单独打包
'element-plus': ['element-plus'],
// 将工具函数打包
'utils': ['@/utils/format', '@/utils/seo'],
// 将编辑器相关打包
'editor': ['marked', 'highlight.js', 'dompurify'],
// 将图表库单独打包
'charts': ['echarts']
}
}
}
}
})
路由懒加载
typescript
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
// 懒加载路由组件
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue'),
meta: { title: '首页' }
},
{
path: '/article/:id',
name: 'Article',
component: () => import('@/views/Article.vue'),
meta: { title: '文章详情' }
},
{
path: '/archive',
name: 'Archive',
component: () => import('@/views/Archive.vue'),
meta: { title: '归档' }
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue'),
meta: { title: '关于' }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
组件懒加载
对于大型组件,可以使用异步组件:
typescript
// 使用defineAsyncComponent
import { defineAsyncComponent } from 'vue'
const Editor = defineAsyncComponent(() =>
import('@/components/editor/EnhancedMarkdownEditor.vue')
)
const Chart = defineAsyncComponent({
loader: () => import('@/components/Chart.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
效果:
- 首屏只加载必要的代码
- 路由切换时才加载对应页面
- 减少初始包体积约40%
2. 懒加载优化
图标懒加载
Element Plus的图标库非常大(几百KB),如果全部加载会严重影响性能。
typescript
// utils/icons.ts
import { registerIcons } from 'element-plus/es/components/icon'
export function lazyRegisterIcons() {
const icons = [
// 只导入需要用到的图标
'Edit',
'Delete',
'View',
'Download',
'Share',
'Star',
'Document',
'Folder',
'Setting',
'User',
'Search',
'Home',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight'
]
// 使用idleCallback在浏览器空闲时注册图标
const idleCallback = window.requestIdleCallback || window.setTimeout
idleCallback(() => {
registerIcons(icons)
})
}
// main.ts
import { lazyRegisterIcons } from './utils/icons'
lazyRegisterIcons()
效果:
- 图标加载减少约200KB
- 不阻塞主线程
- 首屏渲染速度提升15%
图片懒加载
vue
<template>
<img
v-lazy="imageUrl"
:alt="altText"
class="lazy-image"
/>
</template>
<script setup>
import { useIntersectionObserver } from '@vueuse/core'
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }]) => {
if (isIntersecting) {
// 图片进入视口时加载
load()
stop() // 停止观察
}
}
)
</script>
虚拟滚动
对于长列表,使用虚拟滚动:
vue
<template>
<VirtualList
:data-sources="items"
:data-key="'id'"
:keeps="30"
:estimate-size="50"
>
<template #default="{ source }">
<ArticleCard :article="source" />
</template>
</VirtualList>
</template>
<script setup>
import { ref } from 'vue'
import VirtualList from 'vue-virtual-scroll-list'
const items = ref(/* 大量数据 */)
</script>
3. 依赖预构建
优化Vite的预构建配置:
typescript
// vite.config.ts
export default defineConfig({
optimizeDeps: {
// 明确需要预构建的依赖
include: [
'vue',
'vue-router',
'element-plus',
'marked',
'highlight.js',
'dompurify',
'echarts',
'dayjs'
],
// 排除不需要预构建的依赖
exclude: ['your-local-package']
}
})
效果:
- 冷启动时间从3.5s降到1.8s
- HMR更新更快
- 开发体验提升
4. 生产构建优化
代码压缩
typescript
// vite.config.ts
export default defineConfig({
build: {
minify: 'terser',
terserOptions: {
compress: {
// 移除console
drop_console: true,
// 移除debugger
drop_debugger: true,
// 移除无用代码
pure_funcs: ['console.log', 'console.info']
},
format: {
// 移除注释
comments: false
}
},
// 更好的chunk命名策略
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]'
}
},
// chunk大小警告阈值
chunkSizeWarningLimit: 1000
}
})
CSS优化
typescript
// vite.config.ts
import cssnano from 'cssnano'
export default defineConfig({
css: {
postcss: {
plugins: [
cssnano({
preset: 'advanced'
})
]
}
},
build: {
cssCodeSplit: true // 启用CSS代码分割
}
})
资源优化
typescript
// vite.config.ts
export default defineConfig({
build: {
// 资源内联限制
assetsInlineLimit: 4096,
// 小于4KB的图片转为base64
rollupOptions: {
output: {
// 静态资源分类
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.')
const ext = info[info.length - 1]
if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) {
return 'media/[name]-[hash][extname]'
}
if (/\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i.test(assetInfo.name)) {
return 'images/[name]-[hash][extname]'
}
if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) {
return 'fonts/[name]-[hash][extname]'
}
return `${ext}/[name]-[hash][extname]`
}
}
}
}
})
5. 缓存策略
Service Worker
typescript
// public/sw.js
const CACHE_NAME = 'blog-v1'
const urlsToCache = [
'/',
'/index.html',
'/js/*.js',
'/css/*.css',
'/images/*'
]
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) => response || fetch(event.request))
)
})
HTTP缓存
typescript
// vite.config.ts
export default defineConfig({
server: {
headers: {
// 静态资源长期缓存
'Cache-Control': 'public, max-age=31536000, immutable'
}
}
})
6. 图片优化
使用WebP格式
bash
# 安装vite-plugin-imagemin
npm install vite-plugin-imagemin imagemin-webp -D
typescript
// vite.config.ts
import imageminWebp from 'imagemin-webp'
export default defineConfig({
plugins: [
viteImagemin({
plugins: {
webp: imageminWebp({ quality: 75 })
}
})
]
})
响应式图片
vue
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="描述" loading="lazy">
</picture>
7. CDN加速
将静态资源上传到CDN:
typescript
// vite.config.ts
export default defineConfig({
base: 'https://cdn.example.com/blog/',
build: {
rollupOptions: {
output: {
// CDN资源路径
publicPath: 'https://cdn.example.com/blog/'
}
}
}
})
监控与分析
1. Lighthouse
bash
# 安装Lighthouse
npm install -g lighthouse
# 运行测试
lighthouse https://your-blog.com --view
2. WebPageTest
访问 https://www.webpagetest.org/ 进行详细的性能测试。
3. 自定义性能监控
typescript
// utils/performance.ts
export function measurePerformance() {
// 页面加载性能
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
console.log({
DNS查询: perfData.domainLookupEnd - perfData.domainLookupStart,
TCP连接: perfData.connectEnd - perfData.connectStart,
请求: perfData.responseStart - perfData.requestStart,
DOM解析: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
首次渲染: perfData.responseEnd - perfData.fetchStart,
完全加载: perfData.loadEventEnd - perfData.fetchStart
})
})
// 首次内容绘制
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries()
console.log('FCP:', entries[0].startTime)
})
observer.observe({ entryTypes: ['paint'] })
}
优化成果
经过以上优化,取得了显著的性能提升:
1. 加载性能
优化前:
- FCP: 1.8s
- LCP: 2.5s
- TTI: 3.2s
优化后:
- FCP: 0.8s
- LCP: 1.2s
- TTI: 1.8s
2. 包体积
优化前:
- vendor.js: 850KB
- 总体积: 3MB
优化后:
- vendor.js: 520KB
- 总体积: 1.8MB
3. 用户体验
- 首屏可见时间从2.5s降到1.2s
- 页面交互响应更快
- 移动端体验大幅提升
- Lighthouse评分从68提升到92
最佳实践总结
1. 性能优化优先级
高优先级:
✅ 代码分割
✅ 懒加载
✅ 图片优化
✅ 缓存策略
中优先级:
✅ 依赖预构建
✅ 资源压缩
✅ CDN加速
低优先级:
⚠️ Service Worker
⚠️ 预加载
2. 持续优化
typescript
// 定期运行性能测试
npm run analyze
// 检查bundle大小
npm run build:size
// 运行Lighthouse测试
lighthouse https://your-blog.com
3. 工具推荐
- 构建分析:rollup-plugin-visualizer
- 性能测试:Lighthouse、WebPageTest
- 图片优化:vite-plugin-imagemin
- 代码分割:@rollup/plugin-dynamic-import-vars
- 监控工具:Sentry、Google Analytics
总结
性能优化是一个持续的过程,需要:
- 分析问题:找出性能瓶颈
- 制定方案:选择合适的优化策略
- 实施优化:逐步应用优化措施
- 监控效果:持续跟踪性能指标
- 持续改进:根据数据反馈不断优化
记住:过早优化是万恶之源,但性能优化是永恒的话题。
标签:#性能优化 #Vue3 #Vite #前端 #性能调优
点赞❤️ + 收藏⭐️ + 评论💬,你的支持是我创作的动力!