Next.js 入門指南

Next.js 16 App Router 完整入門 — 認識框架定位、路由、Layout、Server/Client Components 與資料擷取的心智模型


目錄


什麼是 Next.js?

Next.js 是基於 React 的全端 Web 框架,由 Vercel 維護。它在 React 的基礎上補齊了路由、伺服器渲染、資料擷取、優化、部署等實務需求,讓開發者可以用一個工具完成從前端 UI 到後端 API 的完整應用

核心特點

  • 🎯 檔案系統路由:把資料夾結構當作 URL 結構,無需手動配置路由
  • Server Components 預設:頁面預設在伺服器端渲染,產出更小的 JS bundle
  • 🔧 內建優化:圖片、字型、腳本、Bundle 都有自動優化機制
  • 📦 全端整合:同一專案內寫 Route Handlers、Server Actions,前後端共用型別
  • 🚀 多種渲染模式:SSG、SSR、ISR、PPR 任你選,甚至可在同一頁混用

為什麼選擇 Next.js?

純 React 的問題

  • 沒有官方路由方案(需自選 React Router 等)
  • 沒有 SSR/SSG 開箱即用
  • 圖片、字型、SEO meta 都要自己處理
  • 部署需要自己配置 Webpack、SSR server

Next.js 的優勢

  • 上述問題全部內建解決
  • SEO 友善(首屏 HTML 直接由伺服器產生,爬蟲拿得到完整內容)
  • 部署到 Vercel 是 zero-config,自架也支援 Docker standalone 模式
  • 大量內建效能優化(自動 code split、image lazy load、font subset)

核心概念

理解 Next.js 之前,先建立 4 個心智模型:

1. App Router vs Pages Router

Next.js 路由演進:

Pages Router(舊版)              App Router(13+ 推薦)
├── pages/                       ├── app/
│   ├── index.tsx                │   ├── page.tsx
│   ├── about.tsx                │   ├── about/
│   └── api/hello.ts             │   │   └── page.tsx
└── 用 getServerSideProps         │   └── api/hello/route.ts
   getStaticProps 取資料          └── 直接在 Server Component 內 await fetch

本系列只談 App Router(Next.js 13+ 推薦,14/15/16 持續強化)。

2. 四種渲染模式

模式 縮寫 何時渲染 適用場景
靜態生成 SSG 建構時 部落格、文件、行銷頁
伺服器渲染 SSR 每次請求 個人化頁面、需即時資料
增量靜態再生 ISR 建構 + 背景重生 半靜態(商品頁、新聞)
部分預渲染 PPR 建構 + Streaming 上述混合(Next.js 15+ 實驗)

→ 深入解析見 rendering-and-cache.md

3. Server Components 與 Client Components

  • Server Component(預設):在伺服器跑、不會送 JS 到瀏覽器、可以直接讀資料庫 / 環境變數
  • Client Component(需 "use client"):傳統 React 元件、有 state、可用 hooks、會被打包送到瀏覽器

→ 邊界判斷見本篇 Server / Client Components 心智模型

4. 全端框架 = 前端 + Route Handlers + Server Actions

Next.js 不只是前端工具,它包含:

  • 前端頁面(page.tsx
  • HTTP API(route.ts — Route Handlers)
  • 直接從前端呼叫伺服器函式(Server Actions)

→ Server Actions 完整介紹見 server-actions.md


快速開始

環境需求

  • Node.js 18.18+(建議 20 LTS 以上)
  • npm / pnpm / yarn / bun 任一

建立新專案

# 使用官方 CLI(推薦)
npx create-next-app@latest my-app

# 互動式選項(推薦選擇)
✔ Would you like to use TypeScript? Yes
✔ Would you like to use ESLint? Yes
✔ Would you like to use Tailwind CSS? Yes
✔ Would you like your code inside a `src/` directory? Yes
✔ Would you like to use App Router? Yes
✔ Would you like to use Turbopack? Yes
✔ Would you like to customize the import alias? No

啟動開發伺服器

cd my-app
npm run dev
# 瀏覽 http://localhost:3000

基本指令

指令 用途
npm run dev 啟動開發伺服器(含 HMR)
npm run build 建構正式版
npm start 啟動正式伺服器
npm run lint 執行 ESLint 檢查

第一個範例

新建 src/app/hello/page.tsx

export default function HelloPage() {
  return (
    <main className="p-8">
      <h1 className="text-2xl font-bold">Hello, Next.js</h1>
      <p>這是我的第一個 Next.js 頁面</p>
    </main>
  );
}

瀏覽 http://localhost:3000/hello 即可看到結果。注意:建立檔案後 URL 立即可用,不需要任何路由設定。


專案結構與檔案約定

標準目錄結構

my-app/
├── src/
│   └── app/                    # App Router 核心目錄
│       ├── layout.tsx          # 根 Layout(必須)
│       ├── page.tsx            # 首頁 /
│       ├── globals.css         # 全域樣式
│       ├── about/
│       │   └── page.tsx        # /about
│       ├── blog/
│       │   ├── page.tsx        # /blog
│       │   └── [slug]/
│       │       └── page.tsx    # /blog/:slug
│       └── api/
│           └── hello/
│               └── route.ts    # API 端點 /api/hello
├── public/                     # 靜態檔案(圖片、favicon)
├── next.config.ts              # Next.js 設定檔
├── tsconfig.json
└── package.json

特殊檔名約定

App Router 用檔名而非設定來定義行為,每個 app/* 資料夾下可以有這些檔案:

檔名 用途
page.tsx 該路由的 UI(讓路由可被存取)
layout.tsx 包裹子頁面的共用 Layout
loading.tsx 載入中的 fallback UI(自動套 Suspense)
error.tsx 錯誤邊界 UI(自動套 ErrorBoundary)
not-found.tsx 404 UI
route.ts HTTP API 端點(Route Handler)
template.tsx 類似 Layout 但每次導航重新 mount
default.tsx Parallel Routes 的預設 slot
app/
└── blog/
    ├── layout.tsx       # /blog/* 共用
    ├── page.tsx         # /blog
    ├── loading.tsx      # /blog 載入時顯示
    ├── error.tsx        # /blog 發生錯誤時顯示
    └── [slug]/
        ├── page.tsx     # /blog/:slug
        └── not-found.tsx # 找不到該文章時顯示

路由與導航入門

基本路由(File-based Routing)

資料夾名稱 = URL segment,內部一定要有 page.tsx 才會變成可訪問路由。

app/
├── page.tsx              → /
├── about/page.tsx        → /about
├── blog/page.tsx         → /blog
└── contact/page.tsx      → /contact

動態路由 [param]

app/blog/[slug]/page.tsx  → /blog/anything
// app/blog/[slug]/page.tsx
type Props = {
  params: Promise<{ slug: string }>;
};

export default async function BlogPost({ params }: Props) {
  const { slug } = await params;
  return <h1>文章:{slug}</h1>;
}

Next.js 15+ 變更params 變成 Promise,必須 await。舊版直接 params.slug 不再適用。

Catch-all 路由 [...slug]

app/docs/[...slug]/page.tsx

匹配 /docs/a/docs/a/b/docs/a/b/c...

const { slug } = await params; // slug: string[] = ["a", "b", "c"]

選擇性 Catch-all [[...slug]]

雙層方括號代表「也匹配無參數」,所以 /docs 本身也會被吃到。

Route Groups (name)

用括號包資料夾 → 不會出現在 URL,純粹組織用:

app/
├── (marketing)/
│   ├── about/page.tsx     → /about
│   └── pricing/page.tsx   → /pricing
└── (shop)/
    ├── cart/page.tsx      → /cart
    └── checkout/page.tsx  → /checkout

用途:

  • 為不同區塊套用不同 Layout
  • 純粹分類用,不影響 URL

靜態跳轉用 <Link>(Server Component 也能用):

import Link from "next/link";

export default function Nav() {
  return (
    <nav>
      <Link href="/">首頁</Link>
      <Link href="/about">關於</Link>
      <Link href="/blog/hello">文章</Link>
    </nav>
  );
}

程式化跳轉用 useRouter(限 Client Component):

"use client";
import { useRouter } from "next/navigation";

export default function LoginButton() {
  const router = useRouter();
  return (
    <button onClick={() => router.push("/dashboard")}>
      登入
    </button>
  );
}

進階路由功能(Parallel Routes、Intercepting Routes、Middleware、Route Handlers 細節)→ 詳見 routing-advanced.md


頁面、Layout、Loading、Error

Page

page.tsx 是該 URL 的進入點:

// app/page.tsx
export default function HomePage() {
  return <h1>歡迎</h1>;
}

Layout

layout.tsx 包裹同層與子層所有頁面,不會在導航時重新 mount

// app/layout.tsx — 根 Layout(必須有)
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-TW">
      <body>
        <header>共用 header</header>
        <main>{children}</main>
        <footer>共用 footer</footer>
      </body>
    </html>
  );
}

Layout 可以巢狀:

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      <aside>側邊選單</aside>
      <section>{children}</section>
    </div>
  );
}

Loading

loading.tsx 會被 Next.js 自動包進 <Suspense>,當該路由的 Server Component 還在取資料時顯示:

// app/blog/loading.tsx
export default function Loading() {
  return <div>載入文章中...</div>;
}

Error

error.tsx 必須是 Client Component(要用 React error boundary):

// app/blog/error.tsx
"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>出錯了!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>重試</button>
    </div>
  );
}

Server / Client Components 心智模型

兩種元件並存

App Router 裡,元件預設是 Server Component。要變成 Client Component,要在檔案最上方加 "use client"

// app/products/page.tsx — Server Component(預設)
import db from "@/lib/db";

export default async function ProductsPage() {
  const products = await db.product.findMany(); // 直接連資料庫
  return <ProductList products={products} />;
}
// app/components/Counter.tsx — Client Component
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

邊界判斷指南

何時要 "use client"

  • 用到 useState / useEffect / 其他 hooks
  • 處理使用者互動(onClick、onChange)
  • 使用瀏覽器 API(window、localStorage)
  • 整合純客戶端套件(如某些圖表庫)

何時保持 Server Component

  • 純展示資料的元件
  • 需要直接 fetch / 讀資料庫 / 讀環境變數
  • 想要更小的 JS bundle
  • 內容對 SEO 重要(伺服器產生 HTML)

邊界注意事項

// ❌ 錯誤:Client Component 不能直接 import Server Component 並執行
"use client";
import ServerComponent from "./ServerComponent"; // 編譯時就會錯

// ✅ 正確:Server Component 可以放 Client Component
// app/page.tsx (Server)
import ClientCounter from "./Counter";

export default function Page() {
  return (
    <div>
      <h1>伺服器渲染的標題</h1>
      <ClientCounter /> {/* OK,Client 元件可被 Server 引用 */}
    </div>
  );
}
// ✅ 正確:透過 children 把 Server 元件「傳」進 Client 元件
"use client";
export default function ClientWrapper({
  children,
}: {
  children: React.ReactNode;
}) {
  return <div className="border">{children}</div>;
}

// 使用方
<ClientWrapper>
  <ServerOnlyContent /> {/* OK */}
</ClientWrapper>

完整邊界規則、Streaming、PPR、四層快取 → 詳見 rendering-and-cache.md


資料擷取入門

Server Component 直接 await fetch

App Router 最大的改變:Server Component 可以是 async function,直接在元件內 await:

// app/posts/page.tsx
type Post = { id: number; title: string };

export default async function PostsPage() {
  const res = await fetch("https://api.example.com/posts");
  const posts: Post[] = await res.json();

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

fetch 的 Next.js 擴充

Next.js 對 fetch 加了三個關鍵選項:

// 1. 靜態快取(預設)
fetch("/api/data", { cache: "force-cache" });

// 2. 不快取(每次請求都打)
fetch("/api/data", { cache: "no-store" });

// 3. 定時重新驗證
fetch("/api/data", { next: { revalidate: 60 } }); // 60 秒重抓

Client Component 取資料

Client Component 用標準 React 方式(useEffect 或 SWR / TanStack Query):

"use client";
import { useEffect, useState } from "react";

export default function ClientPosts() {
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    fetch("/api/posts")
      .then((r) => r.json())
      .then(setPosts);
  }, []);
  return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}

完整快取機制(4 層快取、revalidateTag、revalidatePath、unstable_cache)→ 見 rendering-and-cache.md

資料寫入(表單、Server Actions、樂觀更新)→ 見 server-actions.md


系列導讀

本筆記是 7 篇系列的第一篇,建立基礎心智模型後,依需求選讀:

想了解什麼 該讀哪篇
渲染模式怎麼運作?四層快取是什麼? rendering-and-cache.md
怎麼處理表單與資料寫入? server-actions.md
怎麼做 SEO、OG 圖、sitemap? metadata-and-seo.md
Parallel Routes、Middleware 進階用法? routing-advanced.md
圖片、字型優化、Bundle 分析? assets-and-performance.md
部署到 Vercel / Docker / 環境變數管理? deployment-and-runtime.md

推薦閱讀順序

  1. 本篇(基礎)→ 跑得起 hello world
  2. rendering-and-cache.md → 理解效能與資料流核心
  3. server-actions.md → 學會寫互動表單
  4. 其他四篇依專案需求挑讀

常見問題

問題 1:建立檔案後 URL 顯示 404

症狀:在 app/foo/ 建了資料夾卻 404

原因:資料夾內必須page.tsx 才會變成可訪問路由

解決方案

# 加上 page.tsx
touch app/foo/page.tsx

問題 2:用了 useState 卻噴錯

症狀

You're importing a component that needs `useState`. This React Hook only works in a Client Component.

原因:Server Component(預設)不能用 hooks

解決方案:檔案最上方加 "use client"

"use client";
import { useState } from "react";

問題 3:dynamic route 的 params 報錯

症狀

A param property was accessed directly with `params.slug`. `params` should be awaited...

原因:Next.js 15+ 把 params 改成 Promise

解決方案

// ❌ 舊版
export default function Page({ params }: { params: { slug: string } }) {
  return <h1>{params.slug}</h1>;
}

// ✅ Next.js 15+
export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return <h1>{slug}</h1>;
}

問題 4:環境變數在 Client 抓不到

症狀process.env.MY_VAR 在瀏覽器 console 是 undefined

原因:只有 NEXT_PUBLIC_ 前綴的環境變數才會暴露到 Client

解決方案

# .env.local
DATABASE_URL=...                 # 只在伺服器可用
NEXT_PUBLIC_SITE_URL=...         # 兩邊都可用

→ 完整環境變數規則見 deployment-and-runtime.md


問題 5:跨 Layout 共用狀態

問題:如何在多個頁面之間共用 client state?

解決方案:在 app/layout.tsx 包一個 Client Provider:

// app/providers.tsx
"use client";
import { createContext } from "react";

export const MyContext = createContext(null);

export default function Providers({ children }: { children: React.ReactNode }) {
  return <MyContext.Provider value={...}>{children}</MyContext.Provider>;
}

// app/layout.tsx
import Providers from "./providers";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html><body><Providers>{children}</Providers></body></html>
  );
}

總結

核心要點

Next.js = React + 檔案路由 + Server Components + 全端整合 + 內建優化

關鍵優勢

  • ✅ 檔案系統路由零設定
  • ✅ Server Components 預設,bundle 更小
  • ✅ 多種渲染模式自由切換
  • ✅ 從前端到 API 一站搞定

快速決策指南

情境 建議 理由
純 SPA、不需 SEO ⚠️ 純 React + Vite 可能更輕
內容型網站、部落格 SSG/ISR 是強項
後台儀表板(需登入) Server Components + Route Handlers 完整覆蓋
即時聊天、遊戲 ⚠️ 即時通訊需要額外架設 WebSocket
全端整合需求 Server Actions + API 一站完成

核心檔名速查

檔名 角色
page.tsx 路由 UI
layout.tsx 共用 Layout
loading.tsx 載入 fallback
error.tsx 錯誤邊界
not-found.tsx 404
route.ts API 端點
middleware.ts 中介層(放 src/app/ 同層)

相關閱讀

本系列 7 篇地圖:

  1. basics.md ← 你在這裡
  2. rendering-and-cache.md — 渲染模式與四層快取
  3. server-actions.md — Server Actions 與表單
  4. metadata-and-seo.md — Metadata API 與 SEO
  5. routing-advanced.md — 進階路由
  6. assets-and-performance.md — 資產與效能
  7. deployment-and-runtime.md — 部署與 Runtime

參考資源


建立日期:2026-05-16 最後更新:2026-05-16

🔗相關文章