Website Performance Optimization: Resources, Bandwidth & Speed
目錄 Table of Contents
- 為什麼效能重要
- 效能衡量指標 Core Web Vitals
- HTML 最佳化
- CSS 最佳化
- JavaScript 最佳化
- 圖片與媒體最佳化
- 字型最佳化
- 網路層最佳化:HTTP、快取、壓縮
- 伺服器端最佳化
- 資料庫讀寫最佳化
- CDN 與邊緣運算
- 前端架構策略
- 監控與持續最佳化
1. 為什麼效能重要
1.1 使用者體驗
- 53% 的行動用戶會在頁面載入超過 3 秒時離開(Google 研究)
- 每延遲 100ms,Amazon 報告轉換率下降 1%
- 頁面速度直接影響 SEO 排名(Google 於 2021 年將 Core Web Vitals 列入排名因素)
1.2 成本效益
- 減少頻寬 = 降低雲端費用
- 減少伺服器運算 = 減少 CPU/記憶體用量
- 減少資源大小 = 降低使用者行動數據消耗
2. 效能衡量指標 Core Web Vitals
Google 定義的三大核心指標:
| 指標 | 全名 | 衡量什麼 | 良好 | 需改善 | 差 |
|---|---|---|---|---|---|
| LCP | Largest Contentful Paint | 最大元素渲染時間 | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP | Interaction to Next Paint | 互動後到畫面回應 | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS | Cumulative Layout Shift | 版面位移程度 | ≤ 0.1 | ≤ 0.25 | > 0.25 |
其他重要指標
| 指標 | 說明 |
|---|---|
| FCP (First Contentful Paint) | 首個可見內容繪製時間 |
| TTFB (Time To First Byte) | 從請求到收到第一個位元組 |
| TTI (Time to Interactive) | 頁面變為可互動的時間 |
| TBT (Total Blocking Time) | 主執行緒被阻塞的總時間 |
測量工具
- Google Lighthouse — Chrome DevTools → Lighthouse 分頁
- PageSpeed Insights — https://pagespeed.web.dev/
- WebPageTest — https://www.webpagetest.org/
- Chrome DevTools → Network / Performance — 詳細瀑布圖分析
3. HTML 最佳化
3.1 減少 DOM 大小
DOM 節點越多,瀏覽器渲染越慢(建議 < 1500 節點)。
<!-- ❌ 巢狀過深 -->
<div><div><div><div><p>Text</p></div></div></div></div>
<!-- ✅ 扁平化 -->
<p>Text</p>
3.2 資源載入策略
<!-- CSS: 放在 <head>,不阻塞渲染 -->
<link rel="stylesheet" href="critical.css">
<!-- 非關鍵 CSS: 延遲載入 -->
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">
<!-- JS: defer(不阻塞 HTML 解析,依序執行) -->
<script src="app.js" defer></script>
<!-- JS: async(不阻塞,但不保證順序) -->
<script src="analytics.js" async></script>
3.3 資源預載入 (Resource Hints)
<!-- Preconnect:提前建立連線 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
<!-- DNS Prefetch:提前 DNS 查詢 -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- Preload:提前載入關鍵資源 -->
<link rel="preload" href="hero.webp" as="image">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- Prefetch:預載下一頁資源(低優先) -->
<link rel="prefetch" href="/next-page.html">
<!-- Prerender:預先渲染下一頁 -->
<link rel="prerender" href="/likely-next-page.html">
3.4 壓縮 HTML
- 移除多餘空白、註解
- 工具:
html-minifier-terser
npx html-minifier-terser --collapse-whitespace --remove-comments input.html -o output.html
4. CSS 最佳化
4.1 關鍵 CSS 內嵌 (Critical CSS)
將首屏需要的 CSS 直接內嵌在 <head> 中,其餘延遲載入:
<head>
<style>
/* 首屏關鍵樣式 */
body { margin: 0; font-family: system-ui; }
.hero { height: 100vh; background: #1a5fb4; }
</style>
<link rel="stylesheet" href="full.css" media="print" onload="this.media='all'">
</head>
4.2 移除未使用的 CSS
Chrome DevTools → Coverage 工具可檢查未使用的 CSS。
工具: - PurgeCSS — 分析 HTML 並移除未用到的 CSS - UnCSS — 類似功能
npx purgecss --css styles.css --content index.html --output cleaned.css
4.3 CSS 壓縮
npx cssnano styles.css -o styles.min.css
# 或
npx lightningcss --minify --bundle styles.css -o styles.min.css
4.4 避免昂貴的 CSS 屬性
| 昂貴(觸發重新渲染/佈局) | 高效替代 |
|---|---|
box-shadow(大範圍) |
使用圖片或 filter: drop-shadow() |
border-radius + overflow |
簡化巢狀 |
position: fixed + backdrop-filter |
減少模糊範圍 |
頻繁改變 width/height |
使用 transform: scale() |
改變 top/left |
使用 transform: translate() |
4.5 使用 CSS 硬體加速
.animated {
transform: translateZ(0); /* 觸發 GPU 合成層 */
will-change: transform, opacity; /* 預先告知瀏覽器 */
}
5. JavaScript 最佳化
5.1 程式碼分割 (Code Splitting)
只載入當前頁面需要的 JS:
// 動態 import — 按需載入
const module = await import('./heavy-module.js');
// 路由級分割(React 範例)
const Dashboard = React.lazy(() => import('./Dashboard'));
5.2 Tree Shaking
打包工具(Webpack, Rollup, Vite)可自動移除未使用的 export:
// utils.js
export function usedFunction() { /* ... */ }
export function unusedFunction() { /* ... */ } // 打包時被移除
// app.js
import { usedFunction } from './utils.js';
5.3 壓縮與混淆
# Terser — 業界標準 JS 壓縮
npx terser app.js -o app.min.js --compress --mangle
# esbuild — 極快速壓縮
npx esbuild app.js --minify --outfile=app.min.js
5.4 避免主執行緒阻塞
// ❌ 長時間運算阻塞 UI
function heavyCalculation() {
for (let i = 0; i < 1e9; i++) { /* ... */ }
}
// ✅ 使用 Web Worker 放到背景執行
const worker = new Worker('worker.js');
worker.postMessage({ data: largeData });
worker.onmessage = (e) => { console.log(e.data); };
5.5 減少第三方腳本影響
<!-- 延遲載入非核心第三方腳本 -->
<script src="https://analytics.example.com/script.js" async></script>
<!-- 使用 Intersection Observer 延遲載入 -->
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const script = document.createElement('script');
script.src = 'widget.js';
document.body.appendChild(script);
observer.disconnect();
}
});
});
observer.observe(document.querySelector('#widget-container'));
</script>
5.6 防抖 (Debounce) 與節流 (Throttle)
// Debounce: 等待停止觸發後才執行(搜尋輸入)
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// Throttle: 固定間隔最多執行一次(滾動事件)
function throttle(fn, interval) {
let lastTime = 0;
return (...args) => {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn(...args);
}
};
}
window.addEventListener('scroll', throttle(handleScroll, 100));
input.addEventListener('input', debounce(handleSearch, 300));
6. 圖片與媒體最佳化
6.1 選擇正確的格式
| 格式 | 適用場景 | 壓縮率 | 瀏覽器支援 |
|---|---|---|---|
| WebP | 照片、插圖 | 比 JPEG 小 25-34% | 所有現代瀏覽器 |
| AVIF | 照片(最佳壓縮) | 比 WebP 再小 20% | Chrome, Firefox, Safari 16.4+ |
| SVG | 圖標、向量圖 | 極小 | 全部 |
| JPEG | 照片(回退) | 中等 | 全部 |
| PNG | 需要透明度 | 較大 | 全部 |
6.2 響應式圖片
<picture>
<!-- AVIF 優先(最小) -->
<source srcset="image.avif" type="image/avif">
<!-- WebP 次之 -->
<source srcset="image.webp" type="image/webp">
<!-- JPEG 回退 -->
<img src="image.jpg" alt="描述" width="800" height="600" loading="lazy">
</picture>
<!-- 不同螢幕寬度提供不同大小 -->
<img
srcset="image-400.webp 400w,
image-800.webp 800w,
image-1200.webp 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1024px) 800px,
1200px"
src="image-800.webp"
alt="描述"
width="800" height="600"
loading="lazy"
decoding="async"
>
6.3 懶載入 (Lazy Loading)
<!-- 原生瀏覽器懶載入 -->
<img src="photo.webp" alt="..." loading="lazy" width="400" height="300">
<iframe src="video.html" loading="lazy"></iframe>
6.4 圖片壓縮工具
| 工具 | 類型 | 特點 |
|---|---|---|
| Squoosh (squoosh.app) | 線上 | Google 出品,支援所有格式 |
| Sharp (npm) | CLI / Node.js | 速度極快 |
| ImageOptim | macOS 桌面 | 無損壓縮 |
| TinyPNG | 線上 | PNG/JPEG 壓縮 |
# Sharp CLI 範例
npx sharp-cli -i input.jpg -o output.webp --webp
npx sharp-cli -i input.jpg -o output.avif --avif
6.5 始終指定圖片尺寸
避免 CLS(版面位移):
<!-- ✅ 指定 width/height -->
<img src="photo.webp" alt="..." width="800" height="600">
<!-- 或使用 CSS aspect-ratio -->
<style>
.img-container { aspect-ratio: 4 / 3; }
</style>
7. 字型最佳化
7.1 減少字型大小
/* 只載入需要的字重 */
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2'); /* WOFF2 最小 */
font-weight: 400;
font-display: swap; /* 先顯示系統字型,載入後替換 */
}
7.2 Unicode-range 子集化
只載入需要的字元:
/* 只載入拉丁字元 */
@font-face {
font-family: 'CustomFont';
src: url('font-latin.woff2') format('woff2');
unicode-range: U+0000-00FF;
}
/* 中文字型子集化 */
@font-face {
font-family: 'ChineseFont';
src: url('font-cjk.woff2') format('woff2');
unicode-range: U+4E00-9FFF;
}
7.3 Google Fonts 最佳載入
<!-- 預連線 + 指定字重 + display=swap -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap" rel="stylesheet">
7.4 系統字型堆疊(零網路成本)
body {
font-family:
-apple-system, BlinkMacSystemFont,
'Segoe UI', 'Noto Sans TC', 'Microsoft JhengHei',
Roboto, Oxygen, Ubuntu, sans-serif;
}
8. 網路層最佳化:HTTP、快取、壓縮
8.1 啟用壓縮
| 壓縮方式 | 壓縮率 | 速度 | 支援度 |
|---|---|---|---|
| Brotli (br) | 最佳(比 gzip 小 15-25%) | 較慢壓縮,快速解壓 | 所有現代瀏覽器 |
| Gzip (gz) | 良好 | 快速 | 全部 |
| Zstandard (zstd) | 最佳平衡 | 極快 | 新興支援 |
Nginx 設定:
# Brotli
brotli on;
brotli_types text/html text/css application/javascript application/json image/svg+xml;
brotli_comp_level 6;
# Gzip 回退
gzip on;
gzip_types text/html text/css application/javascript application/json image/svg+xml;
gzip_min_length 1000;
8.2 HTTP 快取策略
# 靜態資源:長期快取 + 內容雜湊檔名
location ~* \.(js|css|webp|avif|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML:不快取或短期快取
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# API 回應:不快取
location /api/ {
add_header Cache-Control "no-store";
}
快取策略表:
| 資源類型 | Cache-Control | 說明 |
|---|---|---|
| HTML | no-cache |
每次驗證是否更新 |
| CSS/JS(有 hash) | max-age=31536000, immutable |
一年,內容變動時改檔名 |
| 圖片(有 hash) | max-age=31536000, immutable |
同上 |
| API 資料 | no-store 或 max-age=60 |
依資料性質而定 |
| 字型 | max-age=31536000 |
字型很少變動 |
8.3 HTTP/2 與 HTTP/3
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 多路複用 | ❌(每連線一個請求) | ✅ | ✅ |
| 標頭壓縮 | ❌ | ✅ (HPACK) | ✅ (QPACK) |
| 伺服器推送 | ❌ | ✅ | ✅ |
| 傳輸協定 | TCP | TCP | QUIC (UDP) |
| 隊頭阻塞 | 有 | 部分解決 | 完全解決 |
8.4 減少 HTTP 請求
- 合併小型 CSS/JS 檔案
- 使用 CSS Sprites 或 SVG Symbols 合併圖標
- 內嵌極小資源(< 1KB 的 SVG、CSS)
- 使用
<link rel="preload">優先載入關鍵資源
8.5 ETag 與條件請求
# 第一次請求
GET /style.css HTTP/2
→ 200 OK
ETag: "abc123"
# 第二次請求(快取驗證)
GET /style.css HTTP/2
If-None-Match: "abc123"
→ 304 Not Modified(不傳送 body,節省頻寬)
9. 伺服器端最佳化
9.1 降低 TTFB
| 方法 | 效果 |
|---|---|
| 使用距離用戶近的伺服器/CDN | 減少網路延遲 |
| 資料庫查詢最佳化(加 index) | 減少伺服器處理時間 |
| 應用層快取(Redis) | 避免重複運算 |
| 連線池 (Connection Pooling) | 減少資料庫連線開銷 |
| 使用 HTTP/2+ | 減少連線建立次數 |
9.2 快取層級
[用戶端快取] → [CDN 快取] → [反向代理快取] → [應用快取 (Redis)] → [資料庫]
←──── 越左邊越快、越便宜 ────→
9.3 Redis 快取範例
import redis
import json
cache = redis.Redis(host='localhost', port=6379)
def get_user(user_id):
cache_key = f"user:{user_id}"
cached = cache.get(cache_key)
if cached:
return json.loads(cached) # 快取命中,極快
user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
cache.setex(cache_key, 3600, json.dumps(user)) # 快取 1 小時
return user
9.4 反向代理 (Reverse Proxy)
[客戶端] → [Nginx 反向代理] → [應用伺服器 (Node.js/Python)]
│
├── 靜態檔案直接回應(不經過應用)
├── 回應壓縮 (Brotli/Gzip)
├── SSL 終止
└── 負載均衡
10. 資料庫讀寫最佳化
10.1 索引 (Index)
-- 為常用查詢欄位加索引
CREATE INDEX idx_users_email ON users (email);
CREATE INDEX idx_orders_user_date ON orders (user_id, created_at);
-- 複合索引:注意欄位順序(高選擇性優先)
CREATE INDEX idx_products_category_price ON products (category, price);
10.2 查詢最佳化
-- ❌ 全表掃描
SELECT * FROM orders WHERE YEAR(created_at) = 2025;
-- ✅ 使用範圍(可用索引)
SELECT * FROM orders
WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01';
-- ❌ SELECT *(浪費頻寬)
SELECT * FROM users;
-- ✅ 只取需要的欄位
SELECT id, name, email FROM users;
-- ❌ N+1 查詢
for user in users:
orders = db.query("SELECT * FROM orders WHERE user_id = ?", user.id)
-- ✅ JOIN 或批次查詢
SELECT u.*, o.* FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
10.3 讀寫分離
[寫入請求] → [主資料庫 (Primary)]
│ 複製
↓
[讀取請求] → [唯讀副本 (Read Replica)] × N
10.4 連線池 (Connection Pooling)
// Node.js + PostgreSQL 連線池
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
database: 'mydb',
max: 20, // 最大連線數
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
11. CDN 與邊緣運算
11.1 CDN (Content Delivery Network)
將靜態資源部署到全球各地的邊緣節點,讓用戶從最近的伺服器取得資源。
[用戶 (台北)] → [CDN 邊緣節點 (台北)] → [原始伺服器 (美國)]
↑ 已快取 → 直接回應(< 20ms)
11.2 常見 CDN 服務
| CDN | 特點 | 免費方案 |
|---|---|---|
| Cloudflare | 全球最大免費 CDN,DDoS 防護 | 慷慨免費方案 |
| AWS CloudFront | 與 AWS 深度整合 | 有免費額度 |
| Fastly | 即時清除快取,邊緣運算 | 有免費額度 |
| Vercel Edge | 自動 CDN + 邊緣函數 | 前端框架最佳 |
| Bunny CDN | 性價比極高 | 付費 |
11.3 邊緣運算 (Edge Computing)
在 CDN 節點上執行程式碼,極度降低延遲:
// Cloudflare Workers 範例
export default {
async fetch(request) {
const cache = caches.default;
let response = await cache.match(request);
if (response) return response;
response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set('Cache-Control', 'max-age=3600');
await cache.put(request, newResponse.clone());
return newResponse;
}
};
12. 前端架構策略
12.1 渲染策略比較
| 策略 | 全名 | LCP | TTI | SEO | 適用 |
|---|---|---|---|---|---|
| SSG | Static Site Generation | 極快 | 快 | 極佳 | 部落格、文件 |
| SSR | Server-Side Rendering | 快 | 中等 | 極佳 | 電商、新聞 |
| CSR | Client-Side Rendering | 慢 | 慢 | 差 | 後台管理 |
| ISR | Incremental Static Regeneration | 極快 | 快 | 極佳 | 大量頁面 |
| Streaming SSR | 分段串流 | 快 | 快 | 佳 | 複雜頁面 |
12.2 Islands Architecture
只有互動部分使用 JavaScript,其餘為純 HTML:
┌──────────────────────────────────────┐
│ 靜態 HTML(不需 JS) │
│ ┌──────────┐ ┌──────────┐ │
│ │ JS Island│ │ JS Island│ │
│ │ (互動元件)│ │ (互動元件)│ │
│ └──────────┘ └──────────┘ │
│ 更多靜態 HTML... │
└──────────────────────────────────────┘
框架:Astro, Fresh (Deno), Qwik
12.3 Service Worker 離線快取
// service-worker.js
const CACHE_NAME = 'v1';
const ASSETS = ['/', '/styles.css', '/app.js', '/offline.html'];
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
);
});
self.addEventListener('fetch', (e) => {
e.respondWith(
caches.match(e.request).then(cached => cached || fetch(e.request))
);
});
13. 監控與持續最佳化
13.1 效能預算 (Performance Budget)
| 資源類型 | 預算上限 |
|---|---|
| HTML | ≤ 50 KB |
| CSS(總計) | ≤ 100 KB |
| JS(總計) | ≤ 300 KB |
| 圖片(每張) | ≤ 200 KB |
| 字型(總計) | ≤ 100 KB |
| 首次載入總大小 | ≤ 1 MB |
13.2 監控工具
| 工具 | 用途 |
|---|---|
| Google Search Console | Core Web Vitals 報告 |
| Lighthouse CI | CI/CD 中自動跑效能測試 |
| Sentry | 前端錯誤與效能監控 |
| Web Vitals (npm) | 程式內收集真實用戶數據 |
// 收集真實用戶 Core Web Vitals
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(metric => sendToAnalytics('LCP', metric));
onINP(metric => sendToAnalytics('INP', metric));
onCLS(metric => sendToAnalytics('CLS', metric));
13.3 最佳化檢查清單
- [ ] 啟用 Brotli/Gzip 壓縮
- [ ] 圖片使用 WebP/AVIF + lazy loading
- [ ] CSS/JS 壓縮 + tree shaking
- [ ] 設定適當的 Cache-Control 標頭
- [ ] 使用 CDN
- [ ] 字型使用
font-display: swap+ preload - [ ] 關鍵 CSS 內嵌
- [ ] JS 使用
defer或async - [ ] 圖片指定
width/height - [ ] 資料庫查詢加索引
- [ ] 啟用 HTTP/2+
- [ ] 設定效能預算
速查總結
| 層級 | 最佳化方法 | 預期效果 |
|---|---|---|
| HTML | 減少 DOM、defer/async、preload | 減少解析時間 |
| CSS | Critical CSS、PurgeCSS、壓縮 | 減少渲染阻塞 |
| JS | Code splitting、tree shaking、壓縮 | 減少下載量與執行時間 |
| 圖片 | WebP/AVIF、響應式、lazy loading | 減少 50-80% 圖片大小 |
| 字型 | WOFF2、子集化、swap | 減少字型載入延遲 |
| 網路 | Brotli、快取、HTTP/2+ | 減少傳輸量與延遲 |
| 伺服器 | Redis、連線池、反向代理 | 降低 TTFB |
| 資料庫 | 索引、讀寫分離、批次查詢 | 加速讀寫 |
| CDN | 邊緣節點、邊緣運算 | 全球低延遲 |