技術 7 分鐘閱讀
網站效能優化指南:讓你的網站飛快
前言
網站速度直接影響使用者體驗和 SEO 排名。Google 的研究指出,頁面載入時間超過 3 秒,53% 的行動裝置使用者會直接離開。這篇文章從前端到後端,分享我在接案專案中常用的效能優化技巧。
先量測再優化
Core Web Vitals
Google 定義了三個核心指標:
- LCP(Largest Contentful Paint):最大內容繪製,應在 2.5 秒以內
- INP(Interaction to Next Paint):互動到下一次繪製,應在 200ms 以內
- CLS(Cumulative Layout Shift):累計版面位移,應低於 0.1
測量工具
- Lighthouse:Chrome DevTools 內建
- PageSpeed Insights:Google 的線上分析工具
- WebPageTest:更詳細的效能測試
圖片優化
圖片通常佔網頁總容量的 50% 以上,是最值得優化的地方。
使用現代格式
<picture>
<source srcset="/images/hero.avif" type="image/avif" />
<source srcset="/images/hero.webp" type="image/webp" />
<img src="/images/hero.jpg" alt="首頁橫幅" />
</picture>
AVIF 比 WebP 再小 20-30%,但瀏覽器支援度稍低,用 <picture> 標籤做漸進式支援。
響應式圖片
<img
srcset="
/images/product-400.webp 400w,
/images/product-800.webp 800w,
/images/product-1200.webp 1200w
"
sizes="(max-width: 640px) 400px, (max-width: 1024px) 800px, 1200px"
src="/images/product-800.webp"
alt="商品圖片"
loading="lazy"
decoding="async"
/>
圖片壓縮自動化
在 Vite 專案中使用 vite-plugin-imagemin:
// vite.config.ts
import imagemin from 'vite-plugin-imagemin';
export default defineConfig({
plugins: [
imagemin({
webp: { quality: 80 },
avif: { quality: 65 },
}),
],
});
前端優化
程式碼分割
React 的 lazy 和 Suspense 可以按需載入元件:
const ProductPage = lazy(() => import('./pages/ProductPage'));
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/products" element={<ProductPage />} />
<Route path="/admin" element={<AdminDashboard />} />
</Routes>
</Suspense>
);
}
Tree Shaking
確保 import 是按需引入:
// 不好:引入整個套件
import _ from 'lodash';
_.debounce(fn, 300);
// 好:只引入需要的函式
import debounce from 'lodash/debounce';
debounce(fn, 300);
字型優化
@font-face {
font-family: 'Noto Sans TC';
src: url('/fonts/NotoSansTC-Regular.woff2') format('woff2');
font-display: swap;
unicode-range: U+4E00-9FFF; /* 只載入中文字元 */
}
font-display: swap 讓瀏覽器先用系統字型顯示文字,等自訂字型載入後再替換,避免 FOIT(Flash of Invisible Text)。
預載入關鍵資源
<head>
<link rel="preload" href="/fonts/NotoSansTC.woff2" as="font" crossorigin />
<link rel="preload" href="/images/hero.webp" as="image" />
<link rel="preconnect" href="https://api.example.com" />
</head>
CSS 優化
移除未使用的 CSS
Tailwind CSS 的 PurgeCSS 會自動處理這件事。確認 content 設定正確:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./index.html',
],
};
避免大量動畫
CSS 動畫只用在 transform 和 opacity 上,這兩個屬性由 GPU 處理,效能最好:
/* 好:使用 transform */
.card:hover {
transform: translateY(-4px);
transition: transform 0.2s ease;
}
/* 不好:觸發 layout 重新計算 */
.card:hover {
margin-top: -4px;
}
後端優化
Laravel 快取策略
// 設定快取
Route::middleware('cache.headers:public;max_age=3600')->group(function () {
Route::get('/api/categories', [CategoryController::class, 'index']);
});
// 資料庫查詢快取
$products = Cache::remember('products:featured', 3600, function () {
return Product::where('featured', true)
->with('category')
->get();
});
壓縮回應
在 Nginx 中啟用 Gzip:
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;
gzip_comp_level 6;
CDN 部署
靜態資源放到 CDN 上,減少伺服器壓力,也能讓全球使用者都有好的載入速度:
ASSET_URL=https://cdn.example.com
進階技巧
Service Worker 快取
// service-worker.js
const CACHE_NAME = 'v1';
const STATIC_ASSETS = [
'/',
'/css/app.css',
'/js/app.js',
'/images/logo.webp',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
虛擬列表
當列表項目超過數百個時,使用虛擬列表只渲染可見區域的元素:
import { useVirtualizer } from '@tanstack/react-virtual';
function ProductList({ products }: { products: Product[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: products.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80,
});
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
transform: `translateY(${virtualItem.start}px)`,
height: `${virtualItem.size}px`,
}}
>
<ProductCard product={products[virtualItem.index]} />
</div>
))}
</div>
</div>
);
}
效能優化清單
- 圖片使用 WebP/AVIF 格式
- 啟用圖片懶載入
- 程式碼分割(Route-based)
- 壓縮 JavaScript 和 CSS
- 啟用 Gzip/Brotli 壓縮
- 設定適當的快取標頭
- 使用 CDN
- 字型優化(swap、subset)
- 減少第三方腳本
- 資料庫查詢優化
結語
效能優化是一個持續的過程,不需要一次做完所有事情。先用 Lighthouse 找出最大的瓶頸,從影響最大的地方開始優化。記住,使用者體驗永遠是最重要的。
#效能優化 #前端 #Core Web Vitals #SEO