Next.js静态生成SSG #
一、SSG基础 #
1.1 什么是SSG #
静态生成是在构建时生成HTML:
text
构建时 → 生成HTML → 部署 → 请求直接返回HTML
1.2 SSG优点 #
- 访问速度快
- CDN友好
- 服务器压力小
- SEO友好
1.3 SSG缺点 #
- 构建时间长
- 更新需要重新部署
- 不适合实时数据
1.4 适用场景 #
- 博客文章
- 产品页面
- 文档网站
- 营销页面
二、静态页面 #
2.1 默认静态 #
tsx
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>{data}</div>
}
默认情况下,Next.js 会静态渲染页面。
2.2 强制静态 #
tsx
export const dynamic = 'force-static'
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache',
})
return <div>{data}</div>
}
2.3 缓存数据 #
tsx
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache',
})
return <div>{data}</div>
}
三、generateStaticParams #
3.1 基本用法 #
tsx
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map(post => ({
slug: post.slug,
}))
}
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
return <article>{post.content}</article>
}
3.2 多参数 #
tsx
export async function generateStaticParams() {
const products = await getProducts()
return products.map(product => ({
category: product.category,
id: product.id.toString(),
}))
}
export default async function Page({ params }: { params: Promise<{ category: string; id: string }> }) {
const { category, id } = await params
const product = await getProduct(category, id)
return <div>{product.name}</div>
}
3.3 捕获所有路由 #
tsx
export async function generateStaticParams() {
return [
{ slug: ['getting-started'] },
{ slug: ['api', 'reference'] },
{ slug: ['guide', 'basics', 'intro'] },
]
}
export default async function Page({ params }: { params: Promise<{ slug: string[] }> }) {
const { slug } = await params
const path = slug.join('/')
return <div>文档: {path}</div>
}
3.4 动态参数 #
tsx
export const dynamicParams = true
export async function generateStaticParams() {
const popularPosts = await getPopularPosts()
return popularPosts.map(post => ({
slug: post.slug,
}))
}
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
return <article>{post.content}</article>
}
dynamicParams = true 允许访问未预生成的路由。
四、静态导出 #
4.1 配置 #
tsx
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export',
}
export default nextConfig
4.2 构建命令 #
bash
npm run build
输出到 out 目录。
4.3 限制 #
静态导出不支持:
- API Routes
- 动态路由(需要generateStaticParams)
- 服务端功能
- 中间件
4.4 图片优化 #
tsx
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export',
images: {
unoptimized: true,
},
}
export default nextConfig
五、静态数据获取 #
5.1 构建时获取 #
tsx
export default async function Page() {
const res = await fetch('https://api.example.com/data', {
cache: 'force-cache',
})
const data = await res.json()
return <div>{data.title}</div>
}
5.2 数据库访问 #
tsx
import { prisma } from '@/lib/prisma'
export default async function Page() {
const posts = await prisma.post.findMany({
where: { published: true },
})
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
5.3 本地文件 #
tsx
import { readFile } from 'fs/promises'
import path from 'path'
export default async function Page() {
const filePath = path.join(process.cwd(), 'data', 'posts.json')
const data = await readFile(filePath, 'utf-8')
const posts = JSON.parse(data)
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
六、元数据生成 #
6.1 静态元数据 #
tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '我的博客',
description: '技术博客',
}
export default function Page() {
return <div>博客内容</div>
}
6.2 动态元数据 #
tsx
import type { Metadata } from 'next'
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.excerpt,
}
}
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
return <article>{post.content}</article>
}
七、站点地图 #
7.1 sitemap.ts #
tsx
import { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPosts()
const postUrls = posts.map(post => ({
url: `https://example.com/blog/${post.slug}`,
lastModified: post.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.8,
}))
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
{
url: 'https://example.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.7,
},
...postUrls,
]
}
7.2 robots.txt #
tsx
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/'],
},
sitemap: 'https://example.com/sitemap.xml',
}
}
八、最佳实践 #
8.1 预生成热门页面 #
tsx
export async function generateStaticParams() {
const posts = await getPosts()
return posts
.filter(post => post.views > 1000)
.map(post => ({
slug: post.slug,
}))
}
8.2 分页静态生成 #
tsx
export async function generateStaticParams() {
const totalPosts = await getTotalPosts()
const postsPerPage = 10
const totalPages = Math.ceil(totalPosts / postsPerPage)
return Array.from({ length: totalPages }, (_, i) => ({
page: (i + 1).toString(),
}))
}
8.3 缓存策略 #
tsx
export default async function Page() {
const staticData = await fetch('/api/static', {
cache: 'force-cache',
})
const semiStaticData = await fetch('/api/semi-static', {
next: { revalidate: 3600 },
})
return (
<div>
<StaticSection data={staticData} />
<SemiStaticSection data={semiStaticData} />
</div>
)
}
九、总结 #
SSG要点:
| 要点 | 说明 |
|---|---|
| 静态页面 | 默认静态渲染 |
| generateStaticParams | 预生成动态路由 |
| 静态导出 | output: ‘export’ |
| 缓存数据 | cache: ‘force-cache’ |
| 元数据 | 静态/动态生成 |
| 站点地图 | sitemap.ts |
下一步,让我们学习增量静态再生!
最后更新:2026-03-28