Next.js的静态生成(SSG)把页面渲染从请求时提前到构建时。对于内容更新不频繁的博客、文档、电商商品页,这种方式能显著降低首屏延迟,并让搜索引擎直接抓取完整HTML。不过动态路由下要生成哪些文件,以及每个文件的数据从哪来,需要getStaticPaths和getStaticProps协同完成。本文用一个文章站示例,逐步拆解它们的参数、返回值和fallback选择。

一、SSG下两个函数的职责边界
在Next.js中,静态生成的核心思路是构建时执行数据查询,把结果序列化后随HTML一起输出。getStaticProps和getStaticPaths都只能在页面文件里导出,组件里不能使用。getStaticProps处理单页数据,getStaticPaths处理动态路由的路径集合。二者不是竞争关系,而是一个负责生成哪些页面,一个负责每个页面填什么数据。
以文章详情页pages/posts/[id].js为例。Next.js构建到该路由时发现存在动态段[id],会先调用getStaticPaths拿到类似[{ params: { id: '1' } }, { params: { id: '2' } }]的路径数组。接着遍历每个路径,把params.id传给getStaticProps。getStaticProps返回的props会被序列化成JSON,供页面组件在构建时渲染。整个过程中浏览器不会执行这些函数,最终只接收HTML和必要的运行时片段。
这种设计的好处是部署产物非常干净。可以直接查看.next/server/pages/posts目录,会看到1.html、2.html等预渲染文件。配合CDN后,这些文件可以被缓存到边缘节点,用户请求几乎不会回源到Node服务。下面是最简单的函数骨架。
export async function getStaticPaths() {
return { paths: [], fallback: false }
}
export async function getStaticProps({ params }) {
return { props: {} }
}
二、getStaticProps的参数与返回值实战
getStaticProps执行的上下文参数包含params、preview、previewData、locale等。对纯静态博客来说,params最常用。如果页面是pages/posts/[id].js,getStaticProps({ params })中的params.id就是getStaticPaths提供的动态段值。构建期间,每个路径都会触发一次该函数。
返回值必须是一个对象,常见字段有props、notFound、redirect和revalidate。props会被注入页面组件。notFound为true时,Next.js生成404页面,适合数据已删除的场景。redirect可以重定向到其他地址。revalidate是增量静态再生成的开关,单位是秒。填60表示页面构建完成后,60秒内的请求直接返回旧静态文件,超过60秒后的下一次访问会触发后台重新生成,同时继续返回旧页面,直到新产物就绪。
下面代码从接口拉取所有文章标题,供列表页使用。如果接口无数据,直接让该路由返回404,避免渲染空列表。
export default function BlogList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
export async function getStaticProps() {
const res = await fetch('https://api.ipipp.com/posts')
const posts = await res.json()
if (!posts || posts.length === 0) {
return { notFound: true }
}
return {
props: { posts },
revalidate: 120
}
}
实际项目中fetch后面可能需要配置请求头、处理异常。getStaticProps内可以使用任何Node端库,比如fs读取本地Markdown、数据库查询等。但它不能访问window或document。如果页面有客户端交互部分,请放在useEffect里单独处理,不要让这些浏览器API参与构建阶段。
三、getStaticPaths的路径生成与fallback策略
getStaticPaths的返回值中,paths数组长度决定构建阶段生成多少个静态页面。对于小型站点,可以在构建时从API分页拉全量数据。对于文章数量特别大的站点,也可以只返回最近发布的前1000条,避免构建时间过长。paths中的params必须和文件名中的动态段完全匹配,参数值需要是字符串。
fallback接受false、true或blocking三种值。false最严格:访问未在paths中列出的路径时直接返回404,适合内容完全固定且路径不会频繁变化的项目。true会在首次访问未预渲染路径时先返回一个临时fallback页面,后台继续调用getStaticProps生成目标页面,完成后把结果推送给前端。blocking则没有临时页面,服务端会阻塞请求直到生成完成,用户等待一段时间后直接拿到完整HTML。
使用true或blocking的好处是内容更新后无需重新构建全站。例如新增一篇文章时,paths里没有它的id,用户访问/posts/999时,blocking模式会当场生成并缓存新页面。true模式需要组件里处理router.isFallback,否则新页面首次访问时props可能为undefined,容易出现渲染错误。
export async function getStaticPaths() {
const res = await fetch('https://api.ipipp.com/posts')
const posts = await res.json()
const paths = posts.map((post) => ({
params: { id: post.id.toString() }
}))
return { paths, fallback: 'blocking' }
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.ipipp.com/posts/${params.id}`)
const post = await res.json()
if (!post) {
return { notFound: true }
}
return {
props: { post },
revalidate: 60
}
}
四、组合使用与常见错误排查
把getStaticPaths和getStaticProps放进同一个动态路由页面后,构建日志会显示每个路径的生成过程。合理的fallback能让项目兼顾构建速度和实时性。比如fallback: 'blocking'配合revalidate: 60,意味着已生成的页面每60秒自动更新,新路径首次访问时即时生成,后续请求可享受静态缓存。
实际开发中容易遇到几个问题。第一,忘记在getStaticPaths返回值里写fallback,Next.js会报错要求明确指定。第二,getStaticProps里对数据接口返回结构假设过强,例如post可能为null但组件直接访问post.title,构建时会导致错误。第三,使用了Node端专有API却想用浏览器测试,需要注意这些函数只在构建或服务端运行。第四,fallback: true时页面渲染必须处理router.isFallback状态。下面是一个带兜底的详情页组件。
import { useRouter } from 'next/router'
export default function Post({ post }) {
const router = useRouter()
if (router.isFallback) {
return <div>正在加载...</div>
}
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
此外,增量静态再生成依赖运行Next.js服务的持久化缓存。如果使用无服务器部署,需要确认平台是否支持ISR。对于纯静态导出(next export),fallback和revalidate会失效,所有动态路径必须在构建时确定。选择部署方式前,最好先明确内容更新频率和构建成本,再决定fallback策略与revalidate配置如何取舍。
Next.js静态生成getStaticPropsgetStaticPaths修改时间:2026-09-18 06:56:31