Next.js 的增量静态再生 (Incremental Static Regeneration,ISR) 是一种强大的功能,它允许你在构建之后更新静态页面,而无需完全重建整个站点。 这结合了静态站点生成 (SSG) 的性能优势和服务器端渲染 (SSR) 的灵活性。

ISR 的工作原理:

  1. 初始构建: 在构建时,Next.js 会像静态站点生成一样预渲染页面,并生成 HTML 文件。
  2. 重新验证: 在运行时,当请求到达页面时,Next.js 会检查页面是否已过期。过期时间由 revalidate 属性控制(以秒为单位)。
  3. 后台重新生成: 如果页面已过期,Next.js 会在后台重新生成该页面。在此期间,Next.js 仍然会提供旧的缓存页面给用户。
  4. 更新缓存: 一旦页面重新生成完成,Next.js 会更新缓存,并在后续请求中提供新的页面。

如何使用 ISR:

getStaticProps 函数中添加 revalidate 属性即可启用 ISR:

import Head from "next/head";
import Layout from "../../app/layout";
import { useRouter } from "next/router";

// pages/post-static/[id].js
// 使用静态生成html例子打包后会生成多个html文件,每个文件对应一个id
function Post({ post, id }: any) {
return (
<Layout>
<div>
<Head>
<title>{`${id}标题`}</title>
<meta name="description" content={`Post${id}页面描述`} />
<meta name="keywords" content={`文章${id}的关键词`} />
<meta
name="viewport"
content="initial-scale=1.0, width=device-width"
/>
{/* 其他SEO相关的标签 */}
</Head>
<h1>{JSON.stringify(post?.data)}</h1>
</div>
</Layout>
);
}

// 获取静态页面数据(如果只有一个页面则不需要此函数)
export async function getStaticProps({ params }: any) {
try {
const res = await fetch(
`http://43.163.243.129:1337/api/tests/${params.id}`
);
if (!res.ok) {
// 如果请求失败,例如 404,则返回 404 状态码
return {
notFound: true,
};
}
const post = await res.json();

return {
props: {
post,
id: params.id,
},
revalidate: 60, // 60秒内访问同一页面不会重新生成页面 每 60 秒重新生成一次页面 (ISR)
};
} catch (error) {
// 处理其他错误,例如网络错误
console.error("Error fetching data:", error);
return {
notFound: true, // 或者 redirect
};
}
}

// 这里是根据id动态生成html文件,可以根据接口返回的数据生成多个html文件
// 新增的页面必须手动刷新模板解析页面才能生效,否则会返回404,更新的文案则不用,会在缓存时间后自动刷新
export async function getStaticPaths() {
const res = await fetch(`http://43.163.243.129:1337/api/tests/`);
const postAll = await res.json();
const paths = postAll.data.map((post: any) => ({
params: {
id: post.id.toString(), // 如果id是数字类型,需要转成字符串类型
},
}));

return {
paths: paths,
// 允许许动态生成页面: 对于那些没有在 paths 数组中明确列出的路径,Next.js 不会立即返回 404 错误。相反,它会在运行时尝试生成对应的页面
// 比如我新增了文章id为3的页面,但是没有在paths中定义,那么访问这个页面的时候,Next.js会尝试生成这个页面,如果生成成功,则返回生成的页面,然后再去刷新模板就会立即生效
fallback: true, // 或者 'blocking' 或 true
};
}

export default Post;

刷新模版

// 创建一个 API 路由 /api/revalidate 来处理重新验证请求

import { NextApiRequest, NextApiResponse } from "next"

// pages/api/revalidate.js (API 路由)
/**
* 调用此 API 路由来重新验证页面(手动刷新缓存页面) post请求 /api/revalidate secret=YOUR_SECRET_TOKEN
* demo http://localhost:3000/api/revalidate {"secret":123,"path":"/"}
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// 从请求体中获取 secret
const { secret, path } = req.body;
console.log(req.body);

// 检查 secret 是否匹配环境变量中的值
if (secret != process.env.REVALIDATION_SECRET) {
return res.status(401).json({ message: 'Invalid secret', code: 401 });
}

try {
await res.revalidate(path || '/'); // 重新验证指定的路径,默认为'/'
return res.json({ revalidated: true, message: 'success', code: 200 });
} catch (err) {
return res.status(500).send('Error revalidating');
}
}