在以前,我們要架網站
還需要用
做後端
FastAPI
需要用
做前端
React
而中間通訊要用
RESTful API
JSON
HTML Form、Query String
HTML
GraphQL
十分的麻煩
Next.js 就是為了解決這個問題而生的 全端框架
// frontend/UserProfile.jsx
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
useEffect(() => {
fetch(`http://localhost:8000/api/user/${userId}`)
.then(res => res.json())
.then(setUser)
}, [userId])
return user ? <p>{user.name}</p> : <p>Loading...</p>
}傳統方法
十分麻煩
Next.js
// app/user/[id]/page.jsx
async function UserProfile({ params }) {
const user = await db.getUser(params.id) // 直接呼叫 DB,不經過 HTTP
return <p>{user.name}</p>
}簡單快速
Next.js 的核心是 React Component
因此,只要學過 React,就能輕鬆學會 Next.js
那我兩者的功能都想用呢?
答案是組合使用,這也是 Next.js 最推薦的架構方式。
核心原則:Server Component 負責資料,Client Component 負責互動。
app/
├── posts/
│ ├── page.jsx ← Server Component(拉資料)
│ └── components/
│ └── LikeButton.jsx ← Client Component(互動)app/
├── posts/
│ ├── page.jsx ← Server Component(拉資料)
│ └── components/
│ └── LikeButton.jsx ← Client Component(互動)// app/posts/page.jsx (Server Component)
import LikeButton from "./components/LikeButton";
import { db } from "@/lib/db";
export default async function PostsPage() {
const post = await db.post.findFirst()
return (
<main>
<h1>Posts</h1>
<LikeButton post={post} />
</main>
);
}// app/posts/components/LikeButton.jsx (Client Component)
"use client";
export default function LikeButton({ post }) {
return (
<button onClick={() => alert(`你已按讚貼文 ${post}`))}>
❤️ 按讚 {post}
</button>
);
}重要限制:資料只能從 Server 網 Client 傳,如果 Client 要用到 Server 的功能,則需要使用 Server Action。
告別傳統 API Call 吧
只要在函式內部加上 "use server"
Next.js 就會自動把函式呼叫轉換成 API,並在伺服器執行
這稱作 inline Server Action,但只能在 Server Side Component 使用
// app/posts/new/page.jsx
export default function NewPostPage() {
async function createPost(formData) {
"use server" // 放在函式內部
const title = formData.get('title')
// 直接存取資料庫
await db.query('INSERT INTO posts (title) VALUES (?)', [title])
revalidatePath('/posts')
}
return (
<form action={createPost}>
<input name="title" placeholder="標題" />
<button type="submit">送出</button>
</form>
)
}或是拆分成多個檔案也可以!這樣就可以在 Client Side Compoent 裡用了!
// app/posts/new/page.jsx
"use client"
import { useState } from 'react'
import { createPost } from '../../../actions'
export default function NewPostPage() {
const [title, setTitle] = useState('')
async function handleClick() {
await createPost(title)
}
return (
<div>
<input
value={title}
onChange={e => setTitle(e.target.value)}
placeholder="標題"
/>
<button onClick={handleClick}>送出</button>
</div>
)
}// app/actions.js
"use server"
import { revalidatePath } from 'next/cache'
export async function createPost(formData) {
const title = formData.get('title')
await db.query('INSERT INTO posts (title) VALUES (?)', [title])
revalidatePath('/posts')
}在 Next.js 中,資料夾結構即是你的路由網址。不需要額外的設定檔。
app/
├── page.jsx // 首頁 (/)
├── about/
│ └── page.jsx // 關於我們 (/about)
└── dashboard/
├── settings/
│ └── page.jsx // 設定頁 (/dashboard/settings)
└── page.jsx // 面板頁 (/dashboard)// app/posts/[id]/page.jsx
export default async function PostPage({ params }) {
// /posts/123/ → params.id =123
const post = await db.getPost(params.id)
return <h1>{post.title}</h1>
}export default function Page({ params }) {
// /post/a/b/c → params.slug = ['a', 'b', 'c']
return <p>{params.slug.join('/')}</p>
}export default function Page({ params }) {
// /post → params.slug = undefined
// /post/a/b → params.slug = ['a', 'b']
return <p>{params.slug?.join('/') ?? '首頁'}</p>
}使用 [slug] 代表 那一段 可以是任意文字
app/
└── posts/
└── [id]/
└── page.jsx ← /posts/1, /posts/2, /posts/abcapp/
└── post/
└── [...slug]/
└── page.jsx ← /post/a, /post/a/b, /post/a/b/c使用 [...slug] 代表 多段 可以是任意文字
使用 [[...slug]] 代表 零或多段 可以是任意文字
app/
└── post/
└── [[...slug]]/
└── page.jsx ← /post 也符合(上面的不行)| React SPA | Next.js | |
|---|---|---|
| 渲染位置 | 瀏覽器 | 伺服器 + 瀏覽器 |
| SEO | 差 | 好 |
| 載入速度 | 慢(等 JS) | 快(HTML 直接有內容) |
| 打包方式 | 把整個網站包再一起 | 只打包必要、精簡的內容 |
| 後端 API | 需要另外建 | 內建 API Route / Server Action |
| 路由 | 需要 React Router | 內建 File-based Routing |
| 適合場景 | 純前端應用 | 高互動後台、Dashboard、內容型網站、電商 |
| React SPA 開發方式 | Next.js |
|---|---|
| XHR / Fetch API (手動發送並解析請求) | Server Component (直接呼叫函式,不需要管底層原理) |
| Express / FastAPI 後端 (分兩個專案,需處理 API 格式一致性) | API Route / Server Action (同一專案,直接呼叫函式,可提供型別檢查) |
| React Router (手動設定路由) | File-based Routing (資料夾結構即路由,清晰易懂) |
以下是一些有趣的相關內容
Next.js 是出了名的常出漏洞,以下是一些最近的嚴重漏洞:
CVE-2025-55183 (5.3分):暴露伺服器端的原始碼
因此,使用 Next.js 時應定時更新,避免受到漏洞影響。
Next.js 是由 Vercel 開發的,儘管它是開源的,但它的部署系統和 Vercel 的環境高度耦合,因此,我們很難將 Next.js 部署到其他託管服務(如 Cloudflare Worker、Netlify 等)
OpenNext 就是為了解決這個問題而生的,旨在使 Next.js 真正實現可移植性——能夠部署在任何平台上,而不僅僅是 Vercel。
為了避免我們被 Vercel 綁架,我很建議大家在專案中使用 OpenNext,讓我們可以隨心所欲的部署我們的 Next.js 網站。
Cloudflare 和 Vercel 一直都不太喜歡彼此。
有一天 Cloudflare 心血來潮,決定用 AI 把 Next.js 重寫,並和 Vite 結合。於是,經過幾個星期,Vinext 就誕生了。Cloudflare 表示,Vinext 速度更快、更穩定、也更容易部署到各種平台。
Vercel 聽到後很生氣,在 X 上罵說這是一個粗糙的 Fork,並指出許多關鍵漏洞。
但這些漏洞讓他感覺就像是一個真正的 Next.js 複製版。
但由於他和 Next.js 不完全相容,且全是 AI slop,還只是實驗性專案。
我覺得我們還是聽聽就好,不要真的使用它。