Next.js 面试题详细答案 - Q12
Q12: generateStaticParams 的作用是什么?它如何与动态路由和静态生成(SSG)配合工作?
generateStaticParams 概述
generateStaticParams 是 Next.js App Router 中的函数,用于在构建时预生成动态路由的静态页面,实现静态站点生成(SSG)。
基本用法
1. 简单的动态路由
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetch('https://jsonplaceholder.typicode.com/posts')
const data = await posts.json()
return data.map((post) => ({
slug: post.id.toString(),
}))
}
async function BlogPost({ params }) {
const post = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.slug}`)
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<p>{data.body}</p>
</article>
)
}
export default BlogPost
2. 多个动态参数
// app/blog/[category]/[slug]/page.js
export async function generateStaticParams() {
const categories = await fetch('https://api.example.com/categories')
const categoriesData = await categories.json()
const params = []
for (const category of categoriesData) {
const posts = await fetch(`https://api.example.com/categories/${category.id}/posts`)
const postsData = await posts.json()
for (const post of postsData) {
params.push({
category: category.slug,
slug: post.slug,
})
}
}
return params
}
async function BlogPost({ params }) {
const post = await fetch(
`https://api.example.com/categories/${params.category}/posts/${params.slug}`
)
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<p>分类: {params.category}</p>
<div>{data.content}</div>
</article>
)
}
export default BlogPost
与静态生成配合工作
1. 构建时生成静态页面
// app/products/[id]/page.js
export async function generateStaticParams() {
const products = await fetch('https://api.example.com/products')
const data = await products.json()
return data.map((product) => ({
id: product.id.toString(),
}))
}
async function ProductPage({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`)
const data = await product.json()
return (
<div>
<h1>{data.name}</h1>
<p>价格: ${data.price}</p>
<p>描述: {data.description}</p>
</div>
)
}
export default ProductPage
2. 结合 ISR 使用
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts')
const data = await posts.json()
return data.map((post) => ({
slug: post.slug,
}))
}
async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: 3600 }, // 1小时重新验证
})
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<div>{data.content}</div>
</article>
)
}
export default BlogPost
实际应用示例
1. 博客系统
// app/blog/[slug]/page.js
export async function generateStaticParams() {
try {
const response = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }, // 1小时缓存
})
const posts = await response.json()
return posts.map((post) => ({
slug: post.slug,
}))
} catch (error) {
console.error('Failed to generate static params:', error)
return []
}
}
async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: 3600 },
})
if (!post.ok) {
notFound()
}
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<div className="meta">
<span>作者: {data.author}</span>
<span>发布时间: {new Date(data.publishedAt).toLocaleDateString()}</span>
</div>
<div className="content">{data.content}</div>
</article>
)
}
export default BlogPost
2. 电商产品页面
// app/products/[category]/[slug]/page.js
export async function generateStaticParams() {
const categories = await fetch('https://api.example.com/categories')
const categoriesData = await categories.json()
const params = []
for (const category of categoriesData) {
const products = await fetch(`https://api.example.com/categories/${category.id}/products`)
const productsData = await products.json()
for (const product of productsData) {
params.push({
category: category.slug,
slug: product.slug,
})
}
}
return params
}
async function ProductPage({ params }) {
const product = await fetch(
`https://api.example.com/products/${params.category}/${params.slug}`,
{
next: { revalidate: 1800 }, // 30分钟重新验证
}
)
if (!product.ok) {
notFound()
}
const data = await product.json()
return (
<div>
<h1>{data.name}</h1>
<p>分类: {params.category}</p>
<p>价格: ${data.price}</p>
<p>描述: {data.description}</p>
<div className="images">
{data.images.map((image) => (
<img key={image.id} src={image.url} alt={image.alt} />
))}
</div>
</div>
)
}
export default ProductPage
3. 文档系统
// app/docs/[...slug]/page.js
export async function generateStaticParams() {
const docs = await fetch('https://api.example.com/docs')
const data = await docs.json()
return data.map((doc) => ({
slug: doc.path.split('/'),
}))
}
async function DocPage({ params }) {
const path = params.slug.join('/')
const doc = await fetch(`https://api.example.com/docs/${path}`, {
next: { revalidate: 3600 },
})
if (!doc.ok) {
notFound()
}
const data = await doc.json()
return (
<div>
<h1>{data.title}</h1>
<div className="breadcrumb">
{params.slug.map((segment, index) => (
<span key={index}>
{segment}
{index < params.slug.length - 1 && ' / '}
</span>
))}
</div>
<div className="content">{data.content}</div>
</div>
)
}
export default DocPage
高级用法
1. 条件生成
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts')
const data = await posts.json()
// 只生成已发布的文章
return data
.filter((post) => post.published)
.map((post) => ({
slug: post.slug,
}))
}
async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<div>{data.content}</div>
</article>
)
}
export default BlogPost
2. 限制生成数量
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts')
const data = await posts.json()
// 只生成前 100 篇文章
return data
.slice(0, 100)
.map((post) => ({
slug: post.slug,
}))
}
async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<div>{data.content}</div>
</article>
)
}
export default BlogPost
3. 错误处理
// app/blog/[slug]/page.js
export async function generateStaticParams() {
try {
const posts = await fetch('https://api.example.com/posts')
if (!posts.ok) {
throw new Error('Failed to fetch posts')
}
const data = await posts.json()
return data.map((post) => ({
slug: post.slug,
}))
} catch (error) {
console.error('Error generating static params:', error)
// 返回空数组,让 Next.js 使用动态渲染
return []
}
}
async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<div>{data.content}</div>
</article>
)
}
export default BlogPost
与 fallback 配合使用
1. 动态渲染未预生成的页面
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts')
const data = await posts.json()
// 只生成部分页面
return data.slice(0, 50).map((post) => ({
slug: post.slug,
}))
}
async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
if (!post.ok) {
notFound()
}
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<div>{data.content}</div>
</article>
)
}
export default BlogPost
性能优化
1. 并行获取数据
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const [postsResponse, categoriesResponse] = await Promise.all([
fetch('https://api.example.com/posts'),
fetch('https://api.example.com/categories'),
])
const [posts, categories] = await Promise.all([
postsResponse.json(),
categoriesResponse.json(),
])
return posts.map((post) => ({
slug: post.slug,
}))
}
async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<div>{data.content}</div>
</article>
)
}
export default BlogPost
2. 缓存数据获取
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }, // 1小时缓存
})
const data = await posts.json()
return data.map((post) => ({
slug: post.slug,
}))
}
async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: 3600 },
})
const data = await post.json()
return (
<article>
<h1>{data.title}</h1>
<div>{data.content}</div>
</article>
)
}
export default BlogPost
最佳实践
1. 合理使用 generateStaticParams
// 适合静态生成的内容
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts')
const data = await posts.json()
return data.map((post) => ({
slug: post.slug,
}))
}
// 不适合静态生成的内容(用户相关、实时数据)
// 不要使用 generateStaticParams,让 Next.js 使用动态渲染
2. 错误处理
export async function generateStaticParams() {
try {
const posts = await fetch('https://api.example.com/posts')
if (!posts.ok) {
throw new Error('Failed to fetch posts')
}
const data = await posts.json()
return data.map((post) => ({
slug: post.slug,
}))
} catch (error) {
console.error('Error generating static params:', error)
return []
}
}
总结
generateStaticParams 的作用:
- 预生成静态页面:在构建时为动态路由生成静态页面
- 提高性能:静态页面加载速度更快
- SEO 优化:搜索引擎可以更好地抓取静态页面
- 减少服务器负载:静态页面不需要服务器渲染
与静态生成配合工作:
- 构建时生成:在构建过程中生成所有静态页面
- ISR 支持:结合重新验证策略实现增量静态再生
- 动态回退:未预生成的页面使用动态渲染
- 性能优化:通过缓存和并行处理提高构建效率
这些特性让 Next.js 能够为动态路由提供静态站点的性能优势。