紀錄工作經驗、相關知識,解決技術相關問題。

Next, React, 網站相關, 資訊相關

Next.js 專案初始化與架構設計:由 React CSR 轉移至 SSR 之工程實務規範

Next.js 專案初始化與架構設計:由 React CSR 轉移至 SSR 之工程實務規範 特色圖片

Next.js 專案初始化與架構設計:由 React CSR 轉移至 SSR 之工程實務規範

在現代前端開發中,當專案規模擴大、搜尋引擎優化(SEO)需求浮現或首屏載入效能(First Contentful Paint, FCP)面臨瓶頸時,開發團隊往往會面臨從傳統 React Client-Side Rendering(CSR)架構轉移至 Server-Side Rendering(SSR)架構的技術抉擇。純粹依賴客戶端渲染的 React 應用程式,在初始請求時僅回傳幾乎是空白的 HTML 與 JavaScript Bundle,這會直接導致搜尋引擎爬蟲無法有效索引頁面內容,並在低階行動裝置上拉長核心網頁指標(Core Web Vitals)的等待時間。

為了在不捨棄 React 生態系的前提下解決上述效能與架構限制,業界多數團隊會選擇採用 Next.js 作為核心框架。這套基於 React 的開源應用框架提供了內建的伺服器端渲染、靜態站點生成(SSG)以及漸進式渲染能力,能有效降低首屏渲染時間並優化頁面檢索表現。

常見問題(Q&A)與程式碼範本快速跳轉

如果不趕時間,建議順著下方章節往下讀,接下來會從預設標準寫法的瓶頸開始,逐一講解底層運作原理與實作細節。

一、Next.js 與傳統 React CSR 的核心架構差異

在進行專案架構評估時,釐清渲染機制的運作流程是確保系統穩定性的關鍵。傳統 React CSR 依賴瀏覽器端下載所有 JavaScript 執行檔後才進行 DOM 樹的建構與資料抓取;而 Next.js 則透過伺服器端節點進行預先渲染(Pre-rendering),直接輸出完整的 HTML 結構至客戶端。

1.1 標準寫法的瓶頸與工程痛點

若直接將大型 React CSR 專案無縫搬遷至 Next.js,經常會遭遇以下 3 大工程瓶頸:

  1. 全域狀態管理與生命週期錯置:原本依賴瀏覽器端 window 或 localStorage 的初始化邏輯,在 SSR 階段執行時會因為伺服器端缺乏該全域物件而直接拋出錯誤。
  2. 第三方套件相容性:部分未針對 SSR 進行優化的 DOM 操作套件或圖表庫,在伺服器端編譯時會發生崩潰,必須改用動態匯入(Dynamic Import)或 ssr: false 設定。
  3. 資料抓取策略重構:從原本在 useEffect 中發送 API 請求的模式,轉變為必須使用 Next.js 規定的資料獲取函式或 Server Components 架構。

1.2 渲染機制與規格對照表

比較維度React (純 CSR 架構)Next.js (SSR / App Router 架構)
首屏渲染 (FCP)較慢,需等待 JS Bundle 下載與解析極快,伺服器直接回傳完整 HTML
SEO 索引能力較差,多數爬蟲無法有效解析非同步渲染內容極佳,爬蟲可直接讀取完整網頁內容
伺服器負載極低,全數運算負擔落在客戶端裝置較高,需由 Node.js 伺服器處理頁面渲染

二、Next.js 關鍵底層機制解析

Next.js 的核心優勢在於其靈活的渲染策略與檔案系統路由(File-system Routing)。透過 App Router 機制,開發者可以明確劃分 Server Components 與 Client Components 的界線。伺服器端組件(RSC)在伺服器端完成執行後,僅會將序列化後的 JSON 傳遞至客戶端,大幅減少了傳輸到瀏覽器的 JavaScript 體積,進而提升整體的執行效能與互動反應速度。

三、生產環境完整程式碼範本

範本一:基礎頁面與資料非同步載入實作

以下為 Next.js App Router 架構下,結合伺服器端資料抓取的標準頁面實作範本:

// app/posts/page.tsx
import React from 'react';

interface Post {
  id: number;
  title: string;
  body: string;
}

async function getPosts(): Promise<Post[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    next: { revalidate: 3600 }, // 每小時進行一次增量靜態再生 (ISR)
  });
  
  if (!res.ok) {
    throw new Error('資料抓取失敗,請檢查網路連線');
  }
  
  return res.json();
}

export default async function PostsPage() {
  const posts = await getPosts();

  return (
    <main className="p-6">
      <h1 className="text-2xl font-bold mb-4">文章列表 (SSR / ISR)</h1>
      <ul className="space-y-4">
        {posts.slice(0, 5).map((post) => (
          <li key={post.id} className="p-4 border rounded shadow-sm">
            <h2 className="font-semibold text-lg">{post.title}</h2>
            <p className="text-gray-600 mt-2">{post.body}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}

範本二:處理瀏覽器專屬物件的動態匯入設定

當必須在頁面中使用依賴 window 物件的第三方套件時,可透過 next/dynamic 進行客戶端渲染隔離:

// app/dashboard/page.tsx
'use client';

import React from 'react';
import dynamic from 'next/dynamic';

// 關閉伺服器端渲染,僅在客戶端載入該組件
const ChartComponent = dynamic(
  () => import('@/components/HeavyChart'),
  { 
    ssr: false,
    loading: () => <p>載入圖表組件中...</p>
  }
);

export default function DashboardPage() {
  return (
    <div className="p-6">
      <h1 className="text-xl font-bold">數據儀表板</h1>
      <ChartComponent />
    </div>
  );
}

四、開發者論壇高頻 Q&A 與排查指南

Q1:為什麼在 Next.js 中使用 localStorage 會出現 window is not defined 的錯誤?

解決思路:
Next.js 的預設渲染機制包含伺服器端渲染(SSR)。當程式碼在 Node.js 伺服器環境中執行時,並沒有瀏覽器的全域物件 window 或 localStorage。
若要在組件中使用這些瀏覽器專屬 API,必須透過 typeof window !== 'undefined' 進行環境判斷,或是將該邏輯寫在 useEffect 內部,確保程式碼僅會在客戶端掛載完成後才執行。

Q2:從傳統 React CSR 轉移至 Next.js 後,原有的狀態管理(如 Redux)需要重構嗎?

解決思路:
不需要全面重構,但需調整初始化邏輯。在 Next.js 的 SSR 架構下,伺服器端每次請求都會建立獨立的渲染環境,若直接共用全域 Store 會導致狀態汙染。
建議在根組件中透過 useRef 或專屬函式庫規範,確保每個請求都能建立獨立的 Store 實例,並利用 hydration 機制將伺服器端初始狀態安全地傳遞至客戶端。

Q3:如何處理第三方套件不支援 Server Components 的問題?

解決思路:
當第三方 UI 套件內部使用了 React Context、useState 或依賴 DOM 物件卻未宣告 'use client' 時,會在編譯或執行階段報錯。
解法是在該第三方組件的外層包裝一層自定義的 Client Component(在檔案最上方加上 'use client'),或使用 Next.js 的 next/dynamic 並設定 ssr: false,強制該組件僅在客戶端載入。

Q4:Next.js 的 App Router 與 Pages Router 在架構上有何本質上的差異?

解決思路:
Pages Router 主要基於傳統的頁面層級檔案結構,資料抓取依賴 getServerSideProps 或 getStaticProps。
而 App Router 則奠基於 React Server Components(RSC)架構,允許開發者直接在非同步組件中透過 fetch 抓取資料,並支援巢狀佈局(Layouts)、載入狀態(Loading UI)與錯誤處理(Error Boundaries),具備更細緻的渲染控制能力。

Q5:部署 Next.js 專案至正式環境時,Node.js 伺服器與靜態產出該如何配置?

解決思路:
Next.js 提供多種部署模式。若使用傳統伺服器部署,可透過 next build 產生 .next 目錄,並使用 next start 啟動 Node.js 服務;若專案多為靜態內容,亦可透過 output: 'export' 輸出純靜態檔案並託管至 CDN。
在容器化部署(如 Docker)情境下,務必參考官方提供的精簡化 Dockerfile 範本,僅複製執行所需的最小檔案與依賴套件,以降低映像檔體積並提升安全性。

參考資料

發表迴響