Next.js 静态生成 (SSG) 📄
🎯 概述
静态生成(Static Site Generation,SSG)是 Next.js 在构建时预渲染页面的方式,生成静态 HTML 文件,提供最佳性能和 SEO 效果。
📦 什么是 SSG?
SSG 工作原理
- 构建时获取数据
- 生成静态 HTML 文件
- 部署到 CDN
- 用户访问时直接返回 HTML
- 客户端激活
SSG 优势
- ⚡ 极快的加载速度
- 🔍 完美的 SEO
- 💰 低服务器成本
- 🌐 易于 CDN 分发
- 🔒 更高的安全性
🚀 App Router 中的 SSG
默认静态生成
tsx
// app/page.tsx
// 默认情况下,没有动态函数的页面会被静态生成
export default function Page() {
return <h1>静态页面</h1>
}带数据的静态生成
tsx
// app/posts/page.tsx
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 => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
)
}动态路由静态生成
tsx
// app/posts/[id]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json())
return posts.map((post) => ({
id: post.id,
}))
}
async function getPost(id: string) {
const res = await fetch(`https://api.example.com/posts/${id}`)
return res.json()
}
export default async function Post({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}📝 Pages Router 中的 SSG
getStaticProps
tsx
// pages/posts/index.tsx
import { GetStaticProps } from 'next'
interface Post {
id: string
title: string
}
interface Props {
posts: Post[]
}
export const getStaticProps: GetStaticProps<Props> = async () => {
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return {
props: {
posts,
},
revalidate: 60, // ISR: 每60秒重新生成
}
}
export default function Posts({ posts }: Props) {
return (
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
)
}getStaticPaths
tsx
// pages/posts/[id].tsx
import { GetStaticPaths, GetStaticProps } from 'next'
export const getStaticPaths: GetStaticPaths = async () => {
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
const paths = posts.map((post) => ({
params: { id: post.id },
}))
return {
paths,
fallback: false, // 404 for other routes
}
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const res = await fetch(`https://api.example.com/posts/${params!.id}`)
const post = await res.json()
return {
props: {
post,
},
}
}
export default function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}🔄 Fallback 模式
fallback: false
tsx
export const getStaticPaths = async () => {
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } },
],
fallback: false, // 其他路径返回 404
}
}fallback: true
tsx
// pages/posts/[id].tsx
import { useRouter } from 'next/router'
export const getStaticPaths = async () => {
return {
paths: [{ params: { id: '1' } }],
fallback: true, // 其他路径会在首次访问时生成
}
}
export default function Post({ post }) {
const router = useRouter()
if (router.isFallback) {
return <div>加载中...</div>
}
return <article>{post.title}</article>
}fallback: 'blocking'
tsx
export const getStaticPaths = async () => {
return {
paths: [],
fallback: 'blocking', // 服务端生成,不显示加载状态
}
}🎨 增量静态再生 (ISR)
App Router ISR
tsx
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 } // 每60秒重新验证
})
return res.json()
}
export default async function Posts() {
const posts = await getPosts()
return <div>{/* ... */}</div>
}Pages Router ISR
tsx
// pages/posts/index.tsx
export const getStaticProps = async () => {
const posts = await fetchPosts()
return {
props: { posts },
revalidate: 60, // 每60秒重新生成
}
}按需重新验证
tsx
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'
import { NextRequest } from 'next/server'
export async function POST(request: NextRequest) {
const path = request.nextUrl.searchParams.get('path')
if (path) {
revalidatePath(path)
return Response.json({ revalidated: true, now: Date.now() })
}
return Response.json({ revalidated: false, now: Date.now() })
}使用:
bash
curl -X POST http://localhost:3000/api/revalidate?path=/posts📊 性能优化
1. 部分预渲染
tsx
// app/posts/[id]/page.tsx
export async function generateStaticParams() {
// 只预渲染热门文章
const popularPosts = await getPopularPosts()
return popularPosts.slice(0, 100).map((post) => ({
id: post.id,
}))
}
export const dynamicParams = true // 允许其他路径动态生成2. 并行数据获取
tsx
async function getPageData() {
const [posts, categories, tags] = await Promise.all([
fetch('https://api.example.com/posts').then(r => r.json()),
fetch('https://api.example.com/categories').then(r => r.json()),
fetch('https://api.example.com/tags').then(r => r.json()),
])
return { posts, categories, tags }
}3. 图片优化
tsx
import Image from 'next/image'
export default function Post({ post }) {
return (
<article>
<Image
src={post.coverImage}
alt={post.title}
width={1200}
height={630}
priority // 优先加载
/>
<h1>{post.title}</h1>
</article>
)
}🎯 实战示例
博客网站
tsx
// app/blog/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // 1小时
})
return res.json()
}
export default async function Blog() {
const posts = await getPosts()
return (
<div className="blog-grid">
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<Link href={`/blog/${post.slug}`}>阅读更多</Link>
</article>
))}
</div>
)
}
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts')
.then(res => res.json())
return posts.map((post) => ({
slug: post.slug,
}))
}
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 }
})
return res.json()
}
export default async function BlogPost({ params }) {
const post = await getPost(params.slug)
return (
<article>
<h1>{post.title}</h1>
<time>{post.publishedAt}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}产品目录
tsx
// app/products/page.tsx
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 300 } // 5分钟
})
return res.json()
}
export default async function Products() {
const products = await getProducts()
return (
<div className="product-grid">
{products.map(product => (
<div key={product.id}>
<Image
src={product.image}
alt={product.name}
width={300}
height={300}
/>
<h3>{product.name}</h3>
<p>{product.price}元</p>
</div>
))}
</div>
)
}📚 最佳实践
1. 选择合适的渲染策略
tsx
// 完全静态 - 内容很少变化
export default async function AboutPage() {
return <div>关于我们</div>
}
// ISR - 内容定期更新
async function getData() {
const res = await fetch('url', {
next: { revalidate: 3600 }
})
return res.json()
}
// 动态 - 内容频繁变化
async function getData() {
const res = await fetch('url', {
cache: 'no-store'
})
return res.json()
}2. 优化构建时间
tsx
// 限制预渲染数量
export async function generateStaticParams() {
const posts = await getPosts()
// 只预渲染最新的100篇文章
return posts.slice(0, 100).map(post => ({
id: post.id,
}))
}
// 允许其他路径按需生成
export const dynamicParams = true3. 错误处理
tsx
export async function generateStaticParams() {
try {
const posts = await getPosts()
return posts.map(post => ({ id: post.id }))
} catch (error) {
console.error('获取文章列表失败:', error)
return [] // 返回空数组,避免构建失败
}
}🔗 相关资源
下一步:学习 Next.js 增量静态再生 (ISR)。