Next.js 資源最佳化
在現代網頁開發中,資源載入的效能直接影響使用者的體驗與 SEO 排名。Next.js 提供了一系列內建的最佳化組件,幫助開發者輕鬆達成 Core Web Vitals(核心網頁指標) 的優異表現,特別是針對 LCP (最大內容繪製) 與 CLS (累積版面配置偏移) 這兩個關鍵指標。
本篇文章將深入探討 next/image、next/font 和 next/script 的進階用法與最佳實踐。
圖片最佳化 (next/image)
圖片通常是網頁中佔用頻寬最大的資源。Next.js 的 Image 組件擴充了標準的 HTML <img> 標籤,提供了自動化的圖片優化功能。
為什麼需要 next/image?
使用傳統 <img> 標籤時,開發者需手動處理:
- Responsive Images:為不同裝置提供不同尺寸的圖片 (
srcset)。 - CLS Prevention:手動計算並保留圖片空間,防止版面跳動。
- Lazy Loading:實作 Intersection Observer 來延遲載入圖片。
- Modern Formats:將圖片轉檔為 WebP 或 AVIF。
next/image 自動解決了上述所有問題。
基礎與遠端圖片
1. 本地圖片 (Local Images)
Next.js 自動偵測本地圖片的寬高,這是最簡單的用法。
import Image from 'next/image';
import profilePic from './me.png'; // 靜態引入
export default function Page() {
return (
<Image
src={profilePic}
alt="作者照片"
placeholder="blur" // 自動產生模糊預覽圖
/>
);
}
2. 遠端圖片 (Remote Images)
若圖片來自外部 URL,你需要手動指定 width 和 height,並在 next.config.js 設定允許的網域。
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'assets.example.com',
},
],
},
};
進階技巧:Custom Loaders
預設情況下,Next.js 會使用自己的 Server 優化圖片。但如果你使用 Cloudinary、Imgix 等專業圖片 CDN,建議使用 loader 直接生成對應的 URL,這能減輕 Next.js 伺服器的負擔。
'use client';
import Image from 'next/image';
// 定義一個 loader 函數
const myLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`;
};
export default function Page() {
return (
<Image
loader={myLoader} // 使用自定義 loader
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
);
}
進階技巧:fill 與 sizes 詳解
當圖片需要「RWD 響應式」或「填滿容器」時,使用 fill 屬性。但這時必須正確設定 sizes,否則瀏覽器可能會下載過大的圖片。
sizes 的意義
sizes 告訴瀏覽器:「在不同的螢幕寬度下,這張圖片預計會佔據多少視窗寬度?」
錯誤範例:不寫 sizes(預設為 100vw)。
- 若你在手機上顯示一張半寬的卡片圖,但
sizes預設100vw,瀏覽器會以為圖片跟螢幕一樣寬,進而下載了 2 倍大的圖片,浪費流量。
正確範例:
import Image from 'next/image';
export default function Card() {
return (
<div style={{ position: 'relative', height: '300px' }}>
<Image
src="/photo.jpg"
alt="風景圖"
fill // 填滿父容器
style={{ objectFit: 'cover' }} // CSS: 裁切以填滿
// 解讀:
// (max-width: 768px) 100vw -> 手機版時,圖片佔滿全寬
// (max-width: 1200px) 50vw -> 平板版時,圖片佔一半寬度
// 33vw -> 桌面版時,圖片約佔 1/3 寬度
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
);
}
事件處理 (onLoad)
當你需要知道圖片何時載入完成(例如:移除自定義的骨架屏),可以使用 onLoad:
'use client';
import Image from 'next/image';
import { useState } from 'react';
export default function Page() {
const [isLoaded, setIsLoaded] = useState(false);
return (
<Image
src="/large-image.jpg"
alt="Large Image"
width={1000}
height={500}
onLoad={(e) => setIsLoaded(true)} // 圖片載入後觸發
className={isLoaded ? 'opacity-100' : 'opacity-0'} // 簡單的淡入效果
/>
);
}
字體最佳化 (next/font)
next/font 會在 build time 下載字體檔案並 self-host,完全移除外部 Google Fonts 請求,消除隱私疑慮並提升速度。
使用多種字體與 Tailwind CSS
在實務上,我們常需要同時使用「標題字體」與「內文字體」。結合 Tailwind CSS Variable 是最優雅的解法。
1. 設定字體 (app/layout.tsx):
import { Inter, Playfair_Display } from 'next/font/google';
// 內文字體
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
display: 'swap',
});
// 標題字體
const playfair = Playfair_Display({
subsets: ['latin'],
variable: '--font-playfair',
display: 'swap',
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${playfair.variable}`}>
<body>{children}</body>
</html>
);
}
2. 設定 Tailwind (tailwind.config.js):
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'], // 對應 Inter
serif: ['var(--font-playfair)'], // 對應 Playfair Display
},
},
},
};
3. 在組件中使用:
<h1 className="font-serif text-4xl">這會是 Playfair Display</h1>
<p className="font-sans">這會是 Inter</p>
Variable Fonts (可變字體)
Next.js 預設支援 Variable Fonts。如果不指定 weight,Next.js 會載入包含所有粗細的可變字體檔案,這通常比載入多個固定粗細的檔案更有效率。
// 不指定 weight,預設載入 Variable Font
const inter = Inter({ subsets: ['latin'] });
若字體不支援 Variable Font(如某些特殊 Google Fonts),則必須明確指定 weight 陣列:
const roboto = Roboto({
weight: ['400', '700'],
subsets: ['latin'],
});
腳本最佳化 (next/script)
為什麼需要 Web Workers?
有些第三方腳本(如 Google Tag Manager, 監控軟體)非常沈重,會佔用主執行緒 (Main Thread),導致 UI 變得卡頓。
Next.js 提供了實驗性的 worker 策略,可以將這些腳本移到 Web Worker 中執行。這通常需要配合 @next/third-parties 套件庫使用。
安裝套件:
npm install @next/third-parties
使用 Google Tag Manager (GTM) 範例:
@next/third-parties 已經封裝好了常用的第三方服務,能自動幫你優化載入。
import { GoogleTagManager } from '@next/third-parties/google';
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
{/* 只需填入 GTM ID,Next.js 會自動處理載入最佳化 */}
<GoogleTagManager gtmId="GTM-XYZ" />
</html>
);
}
next/script 的載入策略回顧
如果你使用原生的 Script 組件,務必根據重要性選擇正確的策略:
| 策略 | 說明 | 適用場景 |
|---|---|---|
afterInteractive | (預設) 頁面 Hydration 後載入 | GA, Pixel, 熱圖分析 |
beforeInteractive | 頁面內容顯示前載入 (阻擋渲染) | Bot 偵測, Cookie Consent |
lazyOnload | 瀏覽器閒置時載入 | 客服 Chatbot, 社群分享按鈕 |
worker | 在 Web Worker 執行 (實驗性) | 極吃效能的運算腳本 |
內聯腳本 (Inline Script) 注意事項
當你需要直接在 HTML 輸出一段 JS,請務必加上 id,這樣 Next.js 才能確保這段腳本只會被執行一次,即便使用者在頁面間切換。
<Script id="google-analytics">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</Script>
總結
- 圖片:優先使用本地圖片自動寬高;遠端圖片善用
loader;RWD 圖片務必設定正確的sizes以節省流量。 - 字體:利用
variable屬性結合 Tailwind CSS,輕鬆管理多字體系統;Variable Fonts 是效能首選。 - 腳本:善用
@next/third-parties來載入 GTM 或 YouTube,或使用lazyOnload將非關鍵腳本延後載入,確保 LCP 不受影響。