Next.js是基于React的全栈框架,内置服务端渲染(SSR)、静态生成(SSG)、API路由等能力,免去了单独搭建后端服务的成本。从Next.js 13开始引入的App Router架构,通过React Server Components实现了更细粒度的渲染控制。以下是Next.js全栈开发的实战配置,覆盖SSR渲染、API Routes、数据获取与部署的完整流程。
App Router架构与渲染模式
Next.js 13+的App Router使用app/目录组织路由,每个目录代表一个路由段,page.tsx为路由组件。渲染模式由文件中导出的组件类型和函数决定:
app/
layout.tsx # 根布局(Server Component)
page.tsx # 首页
blog/
page.tsx # /blog 页面
[slug]/
page.tsx # /blog/:slug 动态路由
api/
users/
route.ts # API路由 /api/users
四种渲染模式的区别:
SSG (Static Site Generation) - 构建时生成HTML,适合内容固定的页面
SSR (Server-Side Rendering) - 每次请求时生成HTML,适合个性化内容
ISR (Incremental Static Regeneration) - 静态生成+定时更新,兼顾性能与时效
RSC (React Server Components) - 服务端组件,零客户端JS开销
Server Components与Client Components
App Router中默认所有组件都是Server Component,在服务端渲染且不打包到客户端JS中。需要交互的组件通过’use client’声明为Client Component:
// app/blog/page.tsx (Server Component - 默认)
import { db } from '@/lib/db'
import { PostCard } from '@/components/PostCard'
// 服务端直接访问数据库,不暴露连接信息到客户端
async function getPosts() {
const posts = await db.post.findMany({
orderBy: { createdAt: 'desc' },
take: 20
})
return posts
}
export default async function BlogPage() {
const posts = await getPosts()
return (
<main>
<h1>博客文章</h1>
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</main>
)
}
// components/PostCard.tsx
// Server Component,无'use client'声明
export function PostCard({ post }: { post: Post }) {
return (
<article>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<time>{post.createdAt.toLocaleDateString()}</time>
</article>
)
}
// components/LikeButton.tsx
'use client' // 声明为Client Component
import { useState } from 'react'
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false)
const [count, setCount] = useState(0)
const handleLike = async () => {
const res = await fetch(`/api/posts/${postId}/like`, {
method: 'POST'
})
if (res.ok) {
setLiked(!liked)
setCount(prev => liked ? prev - 1 : prev + 1)
}
}
return (
<button onClick={handleLike}>
{liked ? '取消' : '点赞'} ({count})
</button>
)
}
数据获取与缓存策略
Next.js扩展了fetch API,内置缓存和重验证支持:
// app/blog/[slug]/page.tsx
// SSR:每次请求都重新获取数据
export default async function PostPage({ params }: { params: { slug: string } }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`, {
cache: 'no-store' // 禁用缓存,每次请求都获取最新数据
})
const post = await res.json()
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
// ISR:静态生成,每60秒重新验证
export default async function BlogList() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 } // 60秒后重新生成
})
const posts = await res.json()
// ...
}
// SSG:构建时获取数据
export default async function AboutPage() {
const res = await fetch('https://api.example.com/about', {
cache: 'force-cache' // 构建时获取并永久缓存
})
// ...
}
动态路由段的参数验证和静态参数生成:
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
// 生成静态路径(SSG模式)
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return posts.map((post: { slug: string }) => ({
slug: post.slug
}))
}
// 动态metadata
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json())
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
images: [post.coverImage]
}
}
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`)
if (!res.ok) {
notFound() // 返回404页面
}
const post = await res.json()
// ...
}
API Routes开发
Next.js的API路由在app/api/目录下定义,使用Route Handler语法:
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { validateToken } from '@/lib/auth'
// GET /api/users - 获取用户列表
export async function GET(request: NextRequest) {
const auth = validateToken(request)
if (!auth) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { searchParams } = new URL(request.url)
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '20')
const users = await db.user.findMany({
skip: (page - 1) * limit,
take: limit,
select: {
id: true,
name: true,
email: true,
createdAt: true
}
})
const total = await db.user.count()
return NextResponse.json({
data: users,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
})
}
// POST /api/users - 创建用户
export async function POST(request: NextRequest) {
const body = await request.json()
// 输入验证
if (!body.email || !body.name) {
return NextResponse.json(
{ error: 'email and name are required' },
{ status: 400 }
)
}
// 检查邮箱是否已存在
const existing = await db.user.findUnique({
where: { email: body.email }
})
if (existing) {
return NextResponse.json(
{ error: 'Email already exists' },
{ status: 409 }
)
}
const user = await db.user.create({
data: {
name: body.name,
email: body.email,
role: body.role || 'user'
}
})
return NextResponse.json(user, { status: 201 })
}
// app/api/users/[id]/route.ts
// GET /api/users/:id
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: 'User not found' }, { status: 404 })
}
return NextResponse.json(user)
}
// DELETE /api/users/:id
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
await db.user.delete({
where: { id: params.id }
})
return new NextResponse(null, { status: 204 })
}
Middleware中间件鉴权
Next.js Middleware在请求进入页面或API前执行,适合做认证、重定向等逻辑:
// middleware.ts (项目根目录)
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value
// 保护/dashboard路由
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!token) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('redirect', request.nextUrl.pathname)
return NextResponse.redirect(loginUrl)
}
}
// 保护API路由(除了登录注册)
if (request.nextUrl.pathname.startsWith('/api')) {
const publicPaths = ['/api/auth/login', '/api/auth/register']
if (!publicPaths.includes(request.nextUrl.pathname) && !token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
}
return NextResponse.next()
}
export const config = {
matcher: [
'/dashboard/:path*',
'/api/:path*'
]
}
部署配置与性能优化
next.config.js配置:
/** @type {import('next').NextConfig} */
const nextConfig = {
// 图片优化
images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [
{ protocol: 'https', hostname: 'cdn.example.com' }
]
},
// 自定义header
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' }
]
}
]
},
// 重写规则(API代理)
async rewrites() {
return [
{
source: '/api/v2/:path*',
destination: 'https://api-v2.example.com/:path*'
}
]
}
}
module.exports = nextConfig
使用Server Actions替代传统API调用(Next.js 14+):
// app/posts/create/page.tsx
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
async function createPost(formData: FormData) {
'use server'
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.post.create({
data: { title, content, authorId: currentUser.id }
})
// 重新生成博客列表页
revalidatePath('/blog')
redirect('/blog')
}
export default function CreatePostPage() {
return (
<form action={createPost}>
<input name="title" type="text" required />
<textarea name="content" required />
<button type="submit">发布</button>
</form>
)
}
Server Actions省去了手写API路由和fetch调用的步骤,表单提交直接在服务端处理,且自动处理CSRF防护。revalidatePath在数据变更后触发对应页面的重新生成,保持SSR内容与数据的实时同步。
Docker容器化部署
# Dockerfile
FROM node:20-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
next.config.js中需开启standalone输出:
const nextConfig = {
output: 'standalone'
}
构建产出的.next/standalone目录包含独立运行所需的最小依赖,无需node_modules,镜像体积可控制在200MB以内。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/nextjs-quan-zhan-kuang-jia-shi-zhan-ssr-xuan-ran-yu/