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