Q12: generateStaticParams 的作用是什么?它如何与动态路由和静态生成(SSG)配合工作?

74 阅读3分钟

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 的作用:

  1. 预生成静态页面:在构建时为动态路由生成静态页面
  2. 提高性能:静态页面加载速度更快
  3. SEO 优化:搜索引擎可以更好地抓取静态页面
  4. 减少服务器负载:静态页面不需要服务器渲染

与静态生成配合工作:

  1. 构建时生成:在构建过程中生成所有静态页面
  2. ISR 支持:结合重新验证策略实现增量静态再生
  3. 动态回退:未预生成的页面使用动态渲染
  4. 性能优化:通过缓存和并行处理提高构建效率

这些特性让 Next.js 能够为动态路由提供静态站点的性能优势。