SSR、CSR 与 SSG 的区别

前端渲染方式主要有三种。CSR(客户端渲染)由浏览器下载空 HTML 和 JS 后在客户端生成页面内容,首屏较慢但后续导航流畅。SSR(服务端渲染)在服务器上生成完整 HTML 再发送给浏览器,首屏速度快且利于 SEO。SSG(静态站点生成)在构建时预先生成所有 HTML 文件,适合内容不频繁变化的页面,性能最优。

Next.js 统一了这三种模式,通过 App Router 提供了默认服务端组件、按需静态生成和流式渲染等能力,开发者无需手动配置即可在不同渲染策略间灵活切换。

App Router 基础

Next.js 13+ 的 App Router 基于 app/ 目录进行路由映射。每个文件夹代表一个路由段,page.tsx 文件使该路由可被访问。layout.tsx 定义共享布局,在子路由切换时不会重新渲染。

// app/layout.tsx - 根布局
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="zh-CN">
      <body>
        <nav>顶部导航</nav>
        {children}
      </body>
    </html>
  )
}

// app/page.tsx - 首页
export default function Home() {
  return <h1>欢迎访问 Next.js 博客</h1>
}

// app/blog/page.tsx - /blog 路由
export default function BlogPage() {
  return <h1>博客列表</h1>
}

服务端组件与客户端组件

App Router 中所有组件默认为服务端组件(RSC)。服务端组件在服务器上执行,不会向客户端发送 JavaScript,可以直接访问数据库、读取文件系统、使用服务端独有的 API。需要交互性和浏览器 API 的组件则需要通过 'use client' 声明为客户端组件。

// app/blog/page.tsx - 服务端组件(默认)
// 可以直接 async,直接访问数据库
import { db } from '@/lib/db'
import { PostList } from './PostList'

export default async function BlogPage() {
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10,
  })

  return (
    <main>
      <h1>最新文章</h1>
      <PostList posts={posts} />
    </main>
  )
}
// app/blog/PostList.tsx - 客户端组件
'use client'

import { useState } from 'react'

export function PostList({ posts }) {
  const [filter, setFilter] = useState('')

  const filtered = posts.filter(p =>
    p.title.includes(filter)
  )

  return (
    <div>
      <input
        value={filter}
        onChange={e => setFilter(e.target.value)}
        placeholder="搜索文章..."
      />
      <ul>
        {filtered.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  )
}

数据获取与缓存

App Router 推荐在服务端组件中直接使用 fetch 进行数据请求,并提供了强大的缓存控制。cache 选项控制请求缓存策略,revalidate 选项控制 ISR(增量静态再生成)的时间间隔。

// 默认缓存(force-cache),构建时请求一次
async function getStaticData() {
  const res = await fetch('https://api.example.com/static', {
    cache: 'force-cache'
  })
  return res.json()
}

// 不缓存,每次请求都重新获取
async function getDynamicData() {
  const res = await fetch('https://api.example.com/live', {
    cache: 'no-store'
  })
  return res.json()
}

// ISR:每 60 秒重新验证一次
async function getISRData() {
  const res = await fetch('https://api.example.com/posts', {
    next: { revalidate: 60 }
  })
  return res.json()
}

// 在页面中使用
export default async function Dashboard() {
  const liveData = await getDynamicData()
  const cachedData = await getISRData()

  return (
    <section>
      <h2>实时数据</h2>
      <pre>{JSON.stringify(liveData, null, 2)}</pre>
      <h2>缓存数据</h2>
      <pre>{JSON.stringify(cachedData, null, 2)}</pre>
    </section>
  )
}

布局与路由嵌套

Next.js 的布局支持深度嵌套。每个路由段都可以有自己的 layout.tsx,子布局嵌套在父布局内部。通过 loading.tsx 可以定义路由切换时的加载状态,配合 Suspense 实现流式渲染。

// app/blog/layout.tsx - 博客专属布局
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="blog-layout">
      <aside>侧边栏:分类、标签</aside>
      <article>{children}</article>
    </div>
  )
}

// app/blog/loading.tsx - 加载状态
export default function BlogLoading() {
  return <div>文章加载中...</div>
}

// app/blog/[slug]/page.tsx - 动态路由
export default async function BlogPost({
  params,
}: {
  params: { slug: string }
}) {
  const post = await fetch(
    `https://api.example.com/posts/${params.slug}`
  ).then(r => r.json())

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}

Metadata API

Next.js 提供了 Metadata API 用于声明式地管理页面的 meta 标签,包括标题、描述、Open Graph 信息等。在服务端组件中导出 metadata 对象或 generateMetadata 函数即可。

// 静态 metadata
export const metadata = {
  title: '博客首页',
  description: '分享前端开发技术文章',
}

// 动态 metadata(根据路由参数生成)
export async function generateMetadata({ params }) {
  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,
      description: post.excerpt,
      images: [post.coverImage],
    },
  }
}

部署

Next.js 应用可以部署到多种平台。Vercel 作为 Next.js 的开发团队,提供了一键部署和零配置体验,自动支持 SSR、ISR 和边缘运行时。自托管场景下,可以使用 next start 启动 Node.js 服务器,或者通过 next export 输出纯静态文件部署到任意静态托管服务。Docker 部署也是企业级应用常用的方式,只需基于 Node.js 镜像安装依赖并运行 next start 即可。