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 即可。