Next.js中服務器端渲染 (SSR) 詳解:動態內容與 SEO 的完美結合
作者:碼力無邊
在上一篇文章中,我們深入探討了靜態站點生成 (SSG) 的強大之處,它通過在構建時預先生成頁面,為用戶提供了極致的訪問速度。但現實世界是動態多變的,并非所有頁面都能在構建時就確定其內容。
想象一下,一個用戶的個人儀表盤,其內容取決于當前登錄的是誰;或者一個電商網站的購物車頁面,其商品列表因人而異。對于這類高度動態化、個性化的內容,我們需要一種能夠在“最后一刻”——也就是在用戶請求到達時——才生成頁面的技術。這,就是服務器端渲染 (SSR) 的舞臺,而 getServerSideProps
則是它的核心執行者。
getServerSideProps
:實時響應的“新聞記者”
讓我們再次回顧那個“新聞記者”的比喻。getServerSideProps
的工作模式就像一位時刻待命的記者:
- 用戶請求到達:瀏覽器向服務器發送一個特定頁面的請求,例如
/dashboard
。 - 服務器執行函數:Next.js 服務器接收到請求,立刻執行該頁面的
getServerSideProps
函數。這個函數可以訪問請求的詳細信息(如 cookies, headers)。 - 獲取實時數據:在函數內部,你可以連接數據庫、調用 API,獲取針對當前用戶或當前時間的最新數據。
- 實時渲染:Next.js 將獲取到的數據作為 props 注入頁面組件,并在服務器上實時渲染出完整的 HTML。
- 返回響應:服務器將這個新鮮出爐的 HTML 發送回瀏覽器。
這個過程確保了用戶看到的永遠是最新、最準確的內容。
何時應該選擇 SSR?
盡管我們強調“盡可能靜態化”,但 SSR 在以下場景中是不可或替代的:
- 高度個性化的內容:頁面內容嚴重依賴于登錄用戶的信息。例如,用戶個人資料頁、訂單歷史、社交媒體的時間線。
- 需要訪問請求對象 (Request Object):你需要根據請求的
headers
、cookies
或查詢參數來決定頁面內容。例如,根據用戶的Accept-Language
頭來決定頁面的語言,或者通過 cookie 判斷用戶登錄狀態并決定是否重定向。 - 數據頻繁且不可預測地變化:頁面的數據變化極快,無法通過 SSG 的定時再生(ISR)來有效更新。例如,股票交易應用的實時行情頁面。
一個常見的誤區:有人認為只要頁面有數據,就應該用 SSR。這是不對的。如果數據對于所有用戶都是一樣的(比如一篇博客),并且不需要每秒鐘都更新,那么 SSG 配合 ISR (后續會講) 是更好的選擇,因為它性能更高。只有當數據必須是“請求級”實時的時候,才需要 SSR。
getServerSideProps
實戰
讓我們構建一個簡單的場景:一個需要用戶登錄才能訪問的個人資料頁面。如果用戶未登錄,我們將他們重定向到登錄頁。
pages/profile.tsx
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import { parse } from 'cookie'; // 一個幫助解析 cookie 的庫type User = {id: string;name: string;email: string;
};// 模擬從數據庫根據用戶ID獲取用戶信息
const fetchUserById = async (userId: string): Promise<User | null> => {// 在真實應用中,這里會進行數據庫查詢if (userId === '123') {return { id: '123', name: '碼力無邊', email: 'user@example.com' };}return null;
};// 1. 定義 getServerSideProps,它接收一個 context 對象
export const getServerSideProps: GetServerSideProps<{ user: User }> = async (context) => {const { req, res } = context; // 從 context 中獲取 request 和 response 對象// 解析請求中的 cookieconst cookies = parse(req.headers.cookie || '');const userId = cookies.auth_token; // 假設我們的登錄 token 存在這里if (!userId) {// 如果沒有 token,說明用戶未登錄// 進行服務器端重定向return {redirect: {destination: '/login', // 重定向到登錄頁permanent: false, // false 表示這是一個臨時重定向},};}const user = await fetchUserById(userId);if (!user) {// 如果 token 無效或用戶不存在,也重定向到登錄頁return {redirect: {destination: '/login',permanent: false,},};}// 如果一切正常,將用戶信息作為 props 傳遞給頁面return {props: {user,},};
};// 2. 頁面組件接收 props
function ProfilePage({ user }: InferGetServerSidePropsType<typeof getServerSideProps>) {return (<div><h1>歡迎回來, {user.name}!</h1><p>您的郵箱是: {user.email}</p></div>);
}export default ProfilePage;
context
對象詳解
getServerSideProps
接收的 context
對象是一個寶庫,它包含了所有關于當前請求的信息:
req
: Node.js 的http.IncomingMessage
對象,包含了請求頭、cookies 等。res
: Node.js 的http.ServerResponse
對象,可以用來設置響應頭。params
: 如果是動態路由,這里會包含路由參數(如[id]
)。query
: URL 的查詢字符串部分,以對象形式表示。resolvedUrl
: 請求的完整 URL 路徑(不含域名)。locale
,locales
: 與國際化 (i18n) 相關的信息。
SSR 的性能考量
SSR 雖然強大,但它是有成本的。每次請求都需要服務器進行計算,這被稱為 TTFB (Time to First Byte) 的開銷。如果你的 getServerSideProps
函數執行緩慢(例如,調用了一個很慢的 API),那么用戶將會看到一個更長的加載等待時間。
優化建議:
- 確保數據源快速:你的數據庫查詢或 API 調用必須足夠快。
- 使用緩存:對于一些可以短時間緩存的數據,考慮在服務器端引入緩存層(如 Redis),避免每次都重復計算或請求。
- 謹慎使用:再次強調,不要濫用 SSR。問問自己:“這個頁面的數據真的需要在每次請求時都是最新的嗎?”如果答案是否定的,請考慮 SSG 或 ISR。
總結:SSG vs. SSR
我們現在已經深入了解了 Next.js 的兩種核心預渲染策略。讓我們用一張最終的對比圖來鞏固記憶:
對比維度 | getStaticProps (SSG) | getServerSideProps (SSR) |
---|---|---|
核心理念 | 構建時一次性生成 | 每次請求實時生成 |
性能 | ?? 極快 (CDN 邊緣分發) | ? 較快 (服務器實時計算) |
適用場景 | 博客、文檔、營銷頁 (內容對所有人一致) | 個人中心、購物車 (內容高度個性化、實時) |
數據新鮮度 | 構建時的快照 | 絕對實時 |
SEO | 💯 完美 | 💯 完美 |
開發中刷新 | 數據只在構建時獲取 | 每次刷新頁面都重新獲取數據 |
理解 SSG 和 SSR 的權衡是成為一名高效 Next.js 開發者的關鍵。它們不是競爭關系,而是互補的工具。一個復雜的應用通常會混合使用這兩種模式:用 SSG 構建大部分公開頁面以獲得最佳性能,用 SSR 來處理需要登錄和個性化的私有頁面。
我們的工具箱里現在有了兩個強大的工具。但如果我想要一個既有 SSG 的速度,又能像 SSR 一樣定期更新內容的“兩全其美”的方案呢?下一篇文章,我們將揭曉 Next.js 的又一個創新功能:增量靜態再生 (ISR),它將打破靜態與動態的界限。敬請期待!