Next.js 服务端渲染 (SSR) 🖥️
🎯 概述
服务端渲染(Server-Side Rendering,SSR)是 Next.js 的核心特性之一,它在服务器上生成 HTML,然后发送到客户端,提供更好的 SEO 和首屏加载性能。
📦 什么是 SSR?
SSR 工作原理
- 用户请求页面
- 服务器执行 React 组件
- 生成完整的 HTML
- 发送 HTML 到客户端
- 客户端激活(Hydration)
SSR vs CSR
| 特性 | SSR | CSR |
|---|---|---|
| 首屏加载 | 快 | 慢 |
| SEO | 优秀 | 较差 |
| 服务器负载 | 高 | 低 |
| 交互延迟 | 低 | 高 |
🚀 App Router 中的 SSR
Server Components(默认)
tsx
// app/page.tsx
// 默认就是服务端组件,自动 SSR
async function getData() {
const res = await fetch('https://api.example.com/data')
return res.json()
}
export default async function Page() {
const data = await getData()
return (
<div>
<h1>{data.title}</h1>
<p>{data.content}</p>
</div>
)
}动态渲染
tsx
// app/posts/[id]/page.tsx
async function getPost(id: string) {
const res = await fetch(`https://api.example.com/posts/${id}`, {
cache: 'no-store' // 每次请求都重新获取
})
return res.json()
}
export default async function Post({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
)
}📝 Pages Router 中的 SSR
getServerSideProps
tsx
// pages/posts/[id].tsx
import { GetServerSideProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
const { id } = context.params!
const res = await fetch(`https://api.example.com/posts/${id}`)
const post = await res.json()
return {
props: {
post,
},
}
}
export default function Post({ post }: Props) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}访问请求信息
tsx
export const getServerSideProps: GetServerSideProps = async (context) => {
const { req, res, query, params } = context
// 获取 cookies
const cookies = req.cookies
// 获取请求头
const userAgent = req.headers['user-agent']
// 获取查询参数
const searchQuery = query.q
return {
props: {
userAgent,
searchQuery,
},
}
}🎨 数据获取模式
并行数据获取
tsx
// app/dashboard/page.tsx
async function getUser() {
const res = await fetch('https://api.example.com/user')
return res.json()
}
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
return res.json()
}
export default async function Dashboard() {
// 并行获取数据
const [user, posts] = await Promise.all([
getUser(),
getPosts(),
])
return (
<div>
<h1>欢迎,{user.name}</h1>
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
</div>
)
}串行数据获取
tsx
// app/profile/page.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 Profile() {
// 先获取用户信息
const user = await getUser('123')
// 再获取用户的文章
const posts = await getUserPosts(user.id)
return (
<div>
<h1>{user.name}</h1>
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
</div>
)
}🔄 缓存策略
强制动态渲染
tsx
// app/page.tsx
export const dynamic = 'force-dynamic'
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>{/* ... */}</div>
}重新验证
tsx
// 每 60 秒重新验证一次
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }
})
return res.json()
}禁用缓存
tsx
async function getData() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
return res.json()
}📊 性能优化
1. 使用 Streaming
tsx
// app/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 3000))
return <div>慢速组件</div>
}
export default function Page() {
return (
<div>
<h1>快速内容</h1>
<Suspense fallback={<div>加载中...</div>}>
<SlowComponent />
</Suspense>
</div>
)
}2. 数据预取
tsx
// app/posts/page.tsx
import Link from 'next/link'
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
return res.json()
}
export default async function Posts() {
const posts = await getPosts()
return (
<div>
{posts.map(post => (
<Link
key={post.id}
href={`/posts/${post.id}`}
prefetch={true} // 预取链接页面
>
{post.title}
</Link>
))}
</div>
)
}3. 部分预渲染
tsx
// app/page.tsx
export const experimental_ppr = true
export default function Page() {
return (
<div>
<StaticHeader />
<Suspense fallback={<Skeleton />}>
<DynamicContent />
</Suspense>
</div>
)
}🎯 实战示例
博客文章页面
tsx
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
interface Post {
slug: string
title: string
content: string
author: string
publishedAt: string
}
async function getPost(slug: string): Promise<Post | null> {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 } // 1小时重新验证
})
if (!res.ok) return null
return res.json()
}
export async function generateMetadata({ params }) {
const post = await getPost(params.slug)
if (!post) return {}
return {
title: post.title,
description: post.content.substring(0, 160),
}
}
export default async function BlogPost({ params }) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
<article>
<h1>{post.title}</h1>
<p>作者:{post.author}</p>
<time>{post.publishedAt}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}用户仪表板
tsx
// app/dashboard/page.tsx
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
async function getUser(token: string) {
const res = await fetch('https://api.example.com/user', {
headers: {
Authorization: `Bearer ${token}`,
},
cache: 'no-store',
})
if (!res.ok) return null
return res.json()
}
export default async function Dashboard() {
const cookieStore = cookies()
const token = cookieStore.get('token')?.value
if (!token) {
redirect('/login')
}
const user = await getUser(token)
if (!user) {
redirect('/login')
}
return (
<div>
<h1>欢迎,{user.name}</h1>
<p>邮箱:{user.email}</p>
</div>
)
}📚 最佳实践
1. 合理使用缓存
tsx
// 静态内容 - 长时间缓存
const staticData = await fetch('url', {
next: { revalidate: 86400 } // 24小时
})
// 动态内容 - 短时间缓存
const dynamicData = await fetch('url', {
next: { revalidate: 60 } // 1分钟
})
// 实时内容 - 不缓存
const realtimeData = await fetch('url', {
cache: 'no-store'
})2. 错误处理
tsx
async function getData() {
try {
const res = await fetch('https://api.example.com/data')
if (!res.ok) throw new Error('获取失败')
return res.json()
} catch (error) {
console.error('数据获取错误:', error)
return null
}
}
export default async function Page() {
const data = await getData()
if (!data) {
return <div>加载失败</div>
}
return <div>{data.title}</div>
}3. 避免客户端状态
tsx
// ❌ 不推荐:在服务端组件中使用客户端状态
export default async function Page() {
const [count, setCount] = useState(0) // 错误!
return <div>{count}</div>
}
// ✅ 推荐:使用客户端组件
'use client'
export default function Counter() {
const [count, setCount] = useState(0)
return <div>{count}</div>
}🔗 相关资源
下一步:学习 Next.js 静态生成 (SSG)。