CHAPTER 08 / FULL-STACK FRAMEWORK

Next.js / Nuxt:
前後端一起寫

React / Vue 只管前端。但網站要 SEO、要快、要伺服器渲染、要 API endpoint,全部要框架解決。 Next.js(基於 React)和 Nuxt(基於 Vue)是現代全端首選。一個 repo 包前端 + 後端,部署一次上線。

  • 會用 Next.js App Router 寫多頁應用
  • 分得清 SSR、SSG、ISR、CSR
  • 會寫 Server Component、Server Action
  • 會寫 API Routes 處理 POST 請求
  • 能架一個有後端的部落格 / SaaS MVP
LESSON 8.1

為什麼要用 Next.js?

純 React 的痛點:

  1. SEO 差:JS 渲染,Google 看到的是空殼
  2. 首屏慢:要等 JS 下載 + 跑完才有畫面
  3. 沒後端:要另開 Express / FastAPI 專案
  4. 要自己配:路由、code splitting、圖片優化都要設定

Next.js 一次解決:檔案路由、SSR/SSG、API endpoint、圖片優化、字型優化都內建。

npx create-next-app@latest my-app
# 選 TypeScript / Tailwind / App Router 都選 yes
LESSON 8.2

App Router:檔案就是路由

app/
├── page.tsx              ← /
├── about/
│   └── page.tsx          ← /about
├── blog/
│   ├── page.tsx          ← /blog
│   └── [slug]/
│       └── page.tsx      ← /blog/任何字串
├── (marketing)/          ← 群組(不影響網址)
│   └── pricing/page.tsx
├── api/
│   └── hello/route.ts    ← /api/hello
├── layout.tsx            ← 共用版型
└── globals.css

page.tsx

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

動態路由

// app/blog/[slug]/page.tsx
export default function BlogPost({
  params
}: { params: { slug: string } }) {
  return <h1>文章 {params.slug}</h1>;
}

共用 layout

// app/layout.tsx
export default function RootLayout({
  children
}: { children: React.ReactNode }) {
  return (
    <html lang="zh-Hant">
      <body>
        <Nav />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}
LESSON 8.3

渲染策略:SSR / SSG / ISR / CSR

策略意思適合
SSGbuild 時產 HTML部落格、行銷頁、文件
SSR每次請求即時產個人化、即時資料
ISR定期重新生成(快取)商品列表、新聞
CSR瀏覽器渲染(傳統 SPA)後台、儀表板

SSG 是「便當前一天做好放冷藏」,SSR 是「客人點了現炒」,ISR 是「半小時做一批熱著」,CSR 是「給你食材自己回家煮」。

App Router 的設定方式

// 預設:靜態(SSG)
export default async function Page() {
  const data = await fetch('https://api.x.com/posts');
  return <Posts data={data} />;
}

// 改成每次請求
export const dynamic = 'force-dynamic';

// ISR:60 秒重新生成
export const revalidate = 60;
LESSON 8.4

Server Component vs Client Component

App Router 的元件預設在伺服器跑。沒有事件、沒有 useState、可以直接 await。

// Server Component(預設)
export default async function Posts() {
  const posts = await db.posts.findMany();  // 直接讀資料庫
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

需要互動時加 'use client'

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

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

原則:盡量 Server Component。只在「真的要互動」(onClick、useState、useEffect)才標 client。bundle size 會小很多。

LESSON 8.5

API Routes & Server Actions

API Route(傳統 REST)

// app/api/users/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const users = await db.users.findMany();
  return NextResponse.json(users);
}

export async function POST(req: Request) {
  const body = await req.json();
  const user = await db.users.create({ data: body });
  return NextResponse.json(user, { status: 201 });
}

Server Action(現代)

不用寫 API endpoint,直接從表單呼叫伺服器函式:

async function createPost(formData: FormData) {
  'use server';
  const title = formData.get('title');
  await db.posts.create({ data: { title } });
}

export default function Page() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button>送出</button>
    </form>
  );
}
LESSON 8.6

Nuxt:Vue 版的 Next

Nuxt 3 的概念跟 Next 99% 一樣,名詞不同。

Next.jsNuxt 3
app/page.tsxpages/index.vue
app/layout.tsxlayouts/default.vue
app/api/x/route.tsserver/api/x.ts
Server Actionserver route + useFetch
middleware.tsmiddleware/auth.ts
npx nuxi@latest init my-app
<!-- pages/index.vue -->
<script setup>
const { data: posts } = await useFetch('/api/posts');
</script>

<template>
  <ul>
    <li v-for="p in posts" :key="p.id">{{ p.title }}</li>
  </ul>
</template>
// server/api/posts.ts
export default defineEventHandler(async () => {
  return await db.posts.findMany();
});
LESSON 8.7

常用內建功能

// 圖片優化
import Image from 'next/image';
<Image src="/cat.jpg" width={400} height={300} alt="貓" />

// 字型優化
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });

// 連結(自動預載)
import Link from 'next/link';
<Link href="/about">關於</Link>

// SEO metadata
export const metadata = {
  title: '我的網站',
  description: '最棒的',
  openGraph: { images: ['/og.png'] }
};

練習:個人部落格

用 Next.js App Router 做一個有後端的部落格:

  1. 首頁列出所有文章(從 markdown 檔讀取)
  2. /blog/[slug] 顯示單篇
  3. /admin 用 Server Action 新增文章
  4. SEO metadata 動態生成
  5. 用 ISR,每 60 秒重整
  6. RSS feed at /rss.xml

常見卡關 FAQ

// Next 學員最常問的問題
Pages Router 還是 App Router?
新專案一律 App Router。它是未來。Pages Router 只在維護舊專案才碰。
為什麼一加 useState 就報錯?
忘記在檔案最上方加 'use client';。Server Component 不能用 hook。
fetch 拿到的資料一直是舊的?
Next 把 fetch 加上強快取。要新鮮資料:fetch(url, {{ cache: 'no-store' }}){{ next: {{ revalidate: 60 }} }}
放 Vercel 之外的地方部署可以嗎?
可以!詳見第 11 章。Cloudflare Pages、Zeabur、Railway 都支援。Vercel 整合最順但不便宜。
SvelteKit、Astro、Remix 也是同類型嗎?
是!全端框架百花齊放。Astro 適合內容站、SvelteKit 體積最小、Remix 已併入 React Router。先學 Next/Nuxt 一個,其他一週上手。
← 上一章
08 Vue