Next.js 全栈框架
Next.js 是 React 的生产级框架,提供 SSR、SSG、ISR、App Router 等特性,是全栈开发的首选。
App Router vs Pages Router
Next.js 13+ 引入 App Router(推荐),基于 React Server Components
app/
├── layout.tsx ← 根布局(持久化,不重新渲染)
├── page.tsx ← 首页 /
├── loading.tsx ← 加载 UI(Suspense 边界)
├── error.tsx ← 错误边界
├── not-found.tsx ← 404 页面
├── (marketing)/ ← 路由组(不影响 URL)
│ ├── about/page.tsx ← /about
│ └── blog/page.tsx ← /blog
├── dashboard/
│ ├── layout.tsx ← 嵌套布局
│ └── page.tsx ← /dashboard
└── api/
└── users/route.ts ← API 路由Server Components vs Client Components
tsx
// Server Component(默认):在服务器渲染,可直接访问数据库
// app/users/page.tsx
async function UsersPage() {
// 直接在组件中 fetch,无需 useEffect
const users = await db.user.findMany()
return (
<div>
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
)
}
// Client Component:需要交互、状态、浏览器 API
// 'use client' 指令标记
'use client'
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
// 组合模式:Server Component 包含 Client Component
// app/dashboard/page.tsx(Server)
async function DashboardPage() {
const data = await fetchDashboardData() // 服务端数据获取
return (
<div>
<h1>Dashboard</h1>
<StaticChart data={data} /> {/* Server Component */}
<InteractiveFilter /> {/* Client Component */}
</div>
)
}数据获取
tsx
// fetch 自动去重和缓存
async function getData() {
// 默认缓存(等同于 SSG)
const res = await fetch('https://api.example.com/data')
// 不缓存(等同于 SSR,每次请求都重新获取)
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// 定时重新验证(ISR)
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 每小时重新验证
})
if (!res.ok) throw new Error('Failed to fetch')
return res.json()
}
// 并行数据获取
async function Page() {
// 同时发起,不串行等待
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
])
return <div>...</div>
}
// 流式渲染(Streaming)
import { Suspense } from 'react'
async function Page() {
return (
<div>
<h1>Dashboard</h1>
{/* 快速内容立即显示 */}
<QuickStats />
{/* 慢速内容流式加载 */}
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>
</div>
)
}路由与导航
tsx
// 动态路由:app/posts/[id]/page.tsx
interface Props {
params: { id: string }
searchParams: { page?: string }
}
async function PostPage({ params, searchParams }: Props) {
const post = await fetchPost(params.id)
const page = Number(searchParams.page ?? 1)
return <PostDetail post={post} page={page} />
}
// 生成静态路径(SSG)
export async function generateStaticParams() {
const posts = await fetchAllPosts()
return posts.map(post => ({ id: String(post.id) }))
}
// 元数据
export async function generateMetadata({ params }: Props) {
const post = await fetchPost(params.id)
return {
title: post.title,
description: post.excerpt,
openGraph: { images: [post.coverImage] }
}
}
// 客户端导航
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
function NavButton() {
const router = useRouter()
const pathname = usePathname()
return (
<button onClick={() => router.push('/dashboard')}>
Go to Dashboard
</button>
)
}API 路由(Route Handlers)
ts
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const page = Number(searchParams.get('page') ?? 1)
const users = await db.user.findMany({
skip: (page - 1) * 10,
take: 10
})
return NextResponse.json({ users, page })
}
export async function POST(request: NextRequest) {
const body = await request.json()
// 验证
const result = userSchema.safeParse(body)
if (!result.success) {
return NextResponse.json({ error: result.error }, { status: 400 })
}
const user = await db.user.create({ data: result.data })
return NextResponse.json(user, { status: 201 })
}
// app/api/users/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const user = await db.user.findUnique({ where: { id: params.id } })
if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 })
return NextResponse.json(user)
}Server Actions
tsx
// 直接在组件中定义服务端操作(无需 API 路由)
'use server'
async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.post.create({ data: { title, content } })
revalidatePath('/posts') // 重新验证缓存
redirect('/posts')
}
// 在表单中使用
function NewPostForm() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">发布</button>
</form>
)
}
// 配合 useFormState 处理错误
'use client'
import { useFormState, useFormStatus } from 'react-dom'
function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>{pending ? '提交中...' : '提交'}</button>
}中间件
ts
// middleware.ts(根目录)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value
// 保护路由
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
// 添加请求头
const response = NextResponse.next()
response.headers.set('x-pathname', request.nextUrl.pathname)
return response
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*']
}总结
- App Router 默认是 Server Components,需要交互才加
'use client' - 数据获取直接在 async 组件中 await,无需 useEffect
- Server Actions 让表单提交无需手写 API 路由
- 用 Suspense + loading.tsx 实现流式渲染,提升感知性能
- 中间件处理认证、重定向等横切关注点