第二章:Next.js App Router 實戰 - 現代化前端架構與極致 SEO 最佳化

這幾年,只要你關注網頁開發趨勢,你一定會聽到全世界都在瘋 Next.js。 如果你以前寫過傳統的 React (例如使用 Create React App 或是 Vite),你一定會覺得 React 很好寫、互動很順暢。 但當你辛辛苦苦把做好的網站部署到網路上,過了一個月,你會發現一個致命的商業問題:你在 Google 完全搜尋不到你的網站內容。

這就是傳統 SPA (Single Page Application,單頁應用程式) 的硬傷。因為 SPA 送給瀏覽器的 HTML 基本上是一片空白(只有一個 <div id="root"></div>),所有的精彩內容都是等使用者的瀏覽器下載完龐大的 JavaScript 之後,才用程式在客戶端「畫」上去的。

對於一個需要靠 SEO (搜尋引擎最佳化) 帶來免費自然流量的「知識付費平台」或是「電商網站」來說,這是毀滅性的打擊。Google 爬蟲一進來,看到白紙一張,就頭也不回地走了。

為了解決這個痛點,Next.js 誕生了。而在 Next.js 13 之後推出的 App Router,更是將前端開發帶入了一個全新的紀元。

🎯 本章目標

  1. 深入理解 Server Components 與 Client Components 的商業價值。
  2. 掌握 App Router 資料夾層級的直覺路由設計。
  3. 實作動態路由 (Dynamic Routes),讓系統能自動承載一萬堂課程。
  4. 學習 Metadata API,打造滿分的 SEO 分享體驗。

🚀 Server Components vs Client Components

在我們這套系統的原始碼中,你會頻繁地看到有些檔案最上方寫著 "use client";,而有些什麼都沒寫。這是 App Router 最核心顛覆性的概念:

1. Server Components (伺服器元件:預設值)

在 App Router 中,如果你不特別宣告,所有的 Component 預設都是 Server Components。 這意味著這些元件會在伺服器端 (Server) 就先執行完畢,把資料抓好、把 React 程式碼運算成乾淨純粹的 HTML 後,再把這個 HTML 傳給使用者的瀏覽器。

🏆 商業級優點:

  • 極致的 SEO:爬蟲一進來就看到完整的文章內容,立刻收錄。
  • 零 JavaScript Bundle:因為在伺服器就渲染完了,這些元件的 JavaScript 程式碼「根本不會傳到使用者的手機裡」。使用者的手機不用花算力去解析 JS,網頁載入速度就像閃電一樣快!這會大幅降低跳出率 (Bounce Rate)。
  • 絕對安全的資料獲取:你可以直接在元件裡面寫資料庫的連線密碼,不用擔心外洩,因為這些 Code 永遠只會在 Server 的機房裡執行。

在 Vibe Tutor 中,我們的課程閱讀頁面 (src/app/courses/[...slug]/page.tsx) 就是最典型的 Server Component。它直接在伺服器讀取本機的 Markdown 檔案並轉換成 HTML,確保你的萬字長文一秒載入,且被 Google 完美收錄。

2. Client Components (客戶端元件)

如果一個元件需要「互動」,例如:

  • 點擊按鈕跳出彈窗 (onClick)
  • 使用 useState 記住狀態
  • 使用 useEffect 監聽瀏覽器捲動
  • 使用需要依賴瀏覽器 API 的動畫庫 (如 Framer Motion)

那它就必須是 Client Component。你只需要在檔案的「第一行」加上魔法宣告:

"use client";

加上這行後,它就會像傳統的 React 一樣,把 JS 傳到瀏覽器端運作了。 在 Vibe Tutor 中,結帳頁面的定價卡片切換 (src/app/pricing/page.tsx) 或是導覽列的手機版漢堡選單,就是 Client Component 的最佳範例。

💡 架構師心法: 盡可能讓你的網頁保持是 Server Component。只有在樹狀結構的「末端葉子節點」(例如一顆會按下去會跳出的按鈕),才將那個小元件切出去變成 Client Component。這能確保整個頁面的效能最大化。


🗺️ 路由設計 (Routing) 的藝術:資料夾即網址

App Router 徹底改變了網址 (URL) 產生的方式,它採用了極度直覺的「資料夾結構路由 (File-system Based Routing)」。

在我們的 src/app/ 資料夾下,每一個有包含 page.tsx 的資料夾,就會自動變成一個網址節點。 例如:

  • src/app/page.tsx ➡️ https://你的網域.com/ (首頁)
  • src/app/pricing/page.tsx ➡️ https://你的網域.com/pricing (定價頁)
  • src/app/dashboard/page.tsx ➡️ https://你的網域.com/dashboard (會員中心)

動態路由 (Dynamic Routes):自動長出無限頁面

那如果我們有 1,000 堂課程呢?總不能手動建 1,000 個資料夾吧? 我們使用了 Next.js 的動態路由功能,也就是資料夾名稱加上中括號:[...slug]

來看看 src/app/courses/[...slug]/page.tsx 這個神妙的設計。 這裡的 [...slug] (稱為 Catch-all Segments) 可以接住任何在 /courses/ 後面的網址層級。

  • 當使用者連到 /courses/car-camping 時,slug 參數就是 ['car-camping']
  • 當連到 /courses/car-camping/01-intro 時,slug 就是 ['car-camping', '01-intro']

我們透過這個 slug 參數,就能在 Server Component 中精準知道使用者現在想看哪一堂課,進而使用 Node.js 的 fs (檔案系統) 去 content/courses/ 底下讀取對應的 Markdown 檔案。

// 擷取自課程頁面原始碼的精華概念
import fs from 'fs';
import path from 'path';

// 這是跑在 Server 端的非同步元件
export default async function CoursePage({ params }: { params: { slug: string[] } }) {
  const { slug } = await params;
  
  // 巧妙地利用陣列組合出伺服器內的絕對路徑
  const filePath = path.join(process.cwd(), "content", "courses", ...slug) + ".md";
  
  try {
    // 瞬間讀取檔案內容
    const fileContents = fs.readFileSync(filePath, "utf8");
    return <div>{/* 渲染 Markdown 內容 */}</div>;
  } catch (error) {
    // 找不到檔案就跳去 404 頁面
    return <div>找不到這堂課喔!</div>;
  }
}

這種設計讓你未來新增任何課程,都完全不需要去動任何一行路由程式碼! 你只要把編輯好的 Markdown 檔案丟進資料夾,網址就自動生成了,這就是一人公司能高效營運的秘密。


📈 效能與分享最佳化:Metadata 與字體

為了讓網站不僅跑得快,還能在社群媒體上引發瘋傳,我們使用了 Next.js 內建的殺手級功能:

  1. 動態 Metadata API: 在社群行銷中,當你把網址貼到 Line 或 Facebook 時,出現的縮圖、標題、描述,決定了點擊率 (CTR)。 只要在頁面中 export const metadata = { ... },Next.js 就會自動幫你把這些資訊塞進 HTML 的 <head> 的 OpenGraph 標籤裡。我們甚至能根據讀取的 Markdown 內容,動態生成這堂課專屬的 SEO 標題!

  2. next/font 閃電字體: 我們在 layout.tsx 中使用了 next/font/google 載入了預設字體。這項技術會在你編譯 (Build) 專案時,將 Google Fonts 直接下載並打包進你的伺服器中。 這完美避免了使用者打開網頁時,還要去 Google 伺服器抓字體所造成的「畫面閃爍與排版偏移 (Cumulative Layout Shift)」。這是提升 Google SEO 核心網頁指標 (Core Web Vitals) 評分的重要小心機。

✅ 本章小結

掌握了 Next.js App Router 的精髓,你的前端架構就已經具備了企業級的穩定度與 SEO 戰鬥力。 有了這副強健的骨架,下一章,我們將來探討如何為它穿上最華麗、最能說服客戶掏錢的外衣:Tailwind CSS 與 Framer Motion 物理微動畫

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!