Next.js服务端渲染SSR #
一、SSR基础 #
1.1 什么是SSR #
服务端渲染是在每次请求时在服务器上生成HTML:
text
请求 → 服务器渲染HTML → 返回完整HTML → 浏览器显示
1.2 SSR优点 #
- SEO友好
- 首屏加载快
- 社交分享支持
- 实时数据
1.3 SSR缺点 #
- 服务器压力大
- TTFB较长
- 需要服务器资源
二、动态渲染 #
2.1 触发动态渲染 #
使用动态函数
tsx
import { cookies, headers } from 'next/headers'
export default async function Page() {
const cookieStore = await cookies()
const headersList = await headers()
const token = cookieStore.get('token')
const userAgent = headersList.get('user-agent')
return <div>{token?.value}</div>
}
禁用缓存
tsx
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'no-store',
})
return <div>{data}</div>
}
强制动态
tsx
export const dynamic = 'force-dynamic'
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>{data}</div>
}
2.2 动态函数 #
| 函数 | 说明 | 示例 |
|---|---|---|
| cookies() | 读取Cookie | cookies().get('token') |
| headers() | 读取Header | headers().get('user-agent') |
| searchParams | 读取查询参数 | searchParams.get('q') |
| draftMode() | 检查草稿模式 | draftMode().isEnabled |
2.3 搜索参数 #
tsx
interface PageProps {
searchParams: Promise<{ q?: string; page?: string }>
}
export default async function SearchPage({ searchParams }: PageProps) {
const { q = '', page = '1' } = await searchParams
const results = await search(q, parseInt(page))
return (
<div>
<h1>搜索: {q}</h1>
<ResultsList results={results} />
</div>
)
}
三、流式渲染 #
3.1 基本用法 #
tsx
import { Suspense } from 'react'
async function SlowComponent() {
const data = await fetch('https://api.example.com/slow')
return <div>{data}</div>
}
export default function Page() {
return (
<div>
<h1>页面标题</h1>
<Suspense fallback={<div>加载中...</div>}>
<SlowComponent />
</Suspense>
</div>
)
}
3.2 多个Suspense #
tsx
export default function Page() {
return (
<div>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<Content />
</Suspense>
<Suspense fallback={<FooterSkeleton />}>
<Footer />
</Suspense>
</div>
)
}
3.3 嵌套Suspense #
tsx
export default function Page() {
return (
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ContentSkeleton />}>
<Content />
</Suspense>
<Footer />
</Suspense>
)
}
3.4 loading.tsx #
text
app/
├── loading.tsx
└── page.tsx
tsx
export default function Loading() {
return (
<div className="animate-pulse space-y-4">
<div className="h-8 bg-gray-200 rounded w-1/2"></div>
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
)
}
四、服务端组件 #
4.1 异步组件 #
tsx
export default async function Page() {
const data = await fetch('https://api.example.com/data')
const result = await data.json()
return <div>{result.title}</div>
}
4.2 数据获取 #
tsx
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`)
return res.json()
}
async function getUserPosts(userId: string) {
const res = await fetch(`https://api.example.com/users/${userId}/posts`)
return res.json()
}
export default async function UserPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const [user, posts] = await Promise.all([
getUser(id),
getUserPosts(id),
])
return (
<div>
<h1>{user.name}</h1>
<PostsList posts={posts} />
</div>
)
}
4.3 数据库访问 #
tsx
import { prisma } from '@/lib/prisma'
export default async function Page() {
const users = await prisma.user.findMany()
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
五、渲染配置 #
5.1 段配置 #
tsx
export const dynamic = 'force-dynamic'
export const dynamicParams = true
export const revalidate = 0
export const fetchCache = 'force-no-store'
export const runtime = 'nodejs'
5.2 配置选项 #
| 配置 | 值 | 说明 |
|---|---|---|
| dynamic | ‘force-dynamic’ | 强制动态渲染 |
| revalidate | 0 | 每次请求验证 |
| fetchCache | ‘force-no-store’ | 不缓存fetch |
5.3 运行时选择 #
tsx
export const runtime = 'edge'
export default async function Page() {
return <div>Edge Runtime</div>
}
| 运行时 | 说明 |
|---|---|
| nodejs | Node.js运行时 |
| edge | Edge运行时 |
六、认证与权限 #
6.1 检查认证 #
tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const session = await auth()
if (!session) {
redirect('/login')
}
return <div>欢迎, {session.user.name}</div>
}
6.2 角色检查 #
tsx
import { auth } from '@/auth'
import { notFound } from 'next/navigation'
export default async function AdminPage() {
const session = await auth()
if (!session || session.user.role !== 'admin') {
notFound()
}
return <div>管理后台</div>
}
6.3 布局认证 #
tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const session = await auth()
if (!session) {
redirect('/login')
}
return (
<div className="flex">
<Sidebar user={session.user} />
<main>{children}</main>
</div>
)
}
七、错误处理 #
7.1 try/catch #
tsx
export default async function Page() {
try {
const data = await fetch('https://api.example.com/data')
if (!data.ok) {
throw new Error('Failed to fetch')
}
const result = await data.json()
return <div>{result.title}</div>
} catch (error) {
return <div>加载失败</div>
}
}
7.2 error.tsx #
tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div>
<h2>出错了!</h2>
<button onClick={reset}>重试</button>
</div>
)
}
7.3 notFound #
tsx
import { notFound } from 'next/navigation'
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getProduct(id)
if (!product) {
notFound()
}
return <div>{product.name}</div>
}
八、最佳实践 #
8.1 并行数据获取 #
tsx
export default async function Page() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json()),
])
return <Dashboard users={users} posts={posts} comments={comments} />
}
8.2 流式渲染优化 #
tsx
export default function Page() {
return (
<div>
<FastContent />
<Suspense fallback={<SlowSkeleton />}>
<SlowContent />
</Suspense>
</div>
)
}
8.3 缓存策略 #
tsx
export default async function Page() {
const staticData = await fetch('/api/static', {
cache: 'force-cache',
})
const dynamicData = await fetch('/api/dynamic', {
cache: 'no-store',
})
return (
<div>
<StaticSection data={staticData} />
<DynamicSection data={dynamicData} />
</div>
)
}
九、总结 #
SSR要点:
| 要点 | 说明 |
|---|---|
| 动态渲染 | 动态函数触发 |
| 流式渲染 | Suspense |
| 服务端组件 | async组件 |
| 认证保护 | session检查 |
| 错误处理 | error.tsx |
下一步,让我们学习静态生成!
最后更新:2026-03-28