本文为翻译作品,原文链接:Crazy Nextjs app router cache in production
NextJs 的缓存是一个设计用来提高网页加载速度和性能的功能。通过缓存,NextJs 能够存储一份页面的静态副本,当用户访问相同页面时,可以直接从缓存中提取,而不是每次都重新生成页面。这不仅减少了服务器的负担,也提高了用户的访问速度。NextJs 提供了几种不同的缓存策略,让开发者可以根据实际需求选择最适合的方案。
然而,对于 Next.js 新手来说,理解并有效利用其缓存系统可能会显得有些复杂和困难。缓存策略的不当使用不仅可能导致应用性能问题,还可能在开发和生产环境中产生不一致的行为,进而影响用户体验。因此,本文旨在为 Next.js 的初学者提供一个关于如何理解和利用 Next.js 缓存机制的指南,帮助你在部署生产应用之前,确保你的应用能够充分利用 Next.js 的缓存策略。
Nextjs 是一个优秀的 React SSR 解决方案,它可以让你的 Web 应用拥有更好的 SEO。从 NextJS 13 开始,新的路由模式 App Rtouer 被引入,但我发现它们在缓存系统方面的知识容易混淆,而且你可能会发现开发构建和生产构建之间的行为有所不同。因此,我相信在你将其部署到生产环境之前,理解他们的新缓存系统是至关重要的。
我想首先提醒的是,对于 NextJs,由于这个缓存系统,请确保在将其部署到生产环境之前测试生产构建。我看到很多 issue 抱怨开发构建和生产构建之间的行为不一致,例子之一:issues/52765。
我个人将他们的缓存系统总结为四个不同的类别。
基于时间的控制
我们可以使用 export const revalidate = 60;
来对我们的路由进行基于时间的控制。请看以下示例。
javascript
export default async function Home() {
const posts = (await (
await fetch('https://jsonplaceholder.typicode.com/posts')
).json()) as Post[];
return (
<main className={styles.main}>
<ol>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/post/${post.id}`}>
{post.id}. {post.body}
</Link>
</li>
))}
</ol>
</main>
);
}
// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config
export const revalidate = 60;
在这个示例中,我们导出了 const revalidate = 60
。这意味着对于 Home 路由,每60秒,它将重新生成这个页面,然后将其用作缓存。在60秒内,其他进入的请求将使用页面的缓存版本。一旦到达60秒,下一个来的请求将会触发页面的再生产过程。然后其他随后的请求将使用新生成的缓存页面。
这适用于一个页面不总是更新,但有时仍然需要更新。要记住的一点是,如果这个 revalidate 值设置为 0,它意味着总是为所有请求生成页面。当它设置为 0 时,没有缓存。
按需重新验证缓存
NextJs 还提供了一个可以重新验证路径缓存的函数 revalidatePath。假设我们有一个页面,它有一些输入字段和一个保存或提交按钮。一旦用户完成编辑并点击保存按钮,然后他们可能被重定向到一些其他页面来显示编辑后的新内容。如果你的重定向页面被缓存了,而你没有做一些处理,那么在重定向后,用户无法看到新内容,因为旧的缓存内容会显示给用户。在这种情况下,在重定向之前,我们可以调用这个 revalidatePath 函数来重新验证重定向页面。
javascript
import { revalidatePath } from 'next/cache';
export const saveAction = () => {
// 在这里执行一些保存动作
// 然后在重定向前,我们可以重新验证新页面
revalidatePath("/");
redirect("/");
}
这在你想让你的大多数页面仍然被缓存以获得更好性能的同时,一旦发生某些操作,你希望页面重新生成以获取最新信息时非常有用。
使用 NextJs 定义的 fetch 进行 HTTP 调用缓存
NextJs 提供了自己的 fetch 函数来帮助处理 HTTP/API 缓存。你可以向这个函数传递一些参数属性来选择退出页面缓存。
javascript
const getPost = async function (id: string) {
revalidatePath(`/post/${id}`);
return await (
await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
next: { revalidate: 0 }, // 看这个属性,它会选择退出缓存
})
).json();
};
在 NextJs 中,我们可以传递 {next:{ revalidate: 0}}
作为第二个参数。当你传递这个参数时,NextJs 将会选择退出缓存,并总是获取数据而不是缓存返回的响应。这个 revalidate 的默认值是 false,这意味着会强制进行 HTTP 缓存。如果你不希望有这种行为,例如,你的 API 返回是动态的,那么你需要设置 {next:{ revalidate: 0}}
或使用其他 HTTP 客户端。你也可以将这个 revalidate 设置为一个小数字,让它只缓存一小段时间。
完全禁用路由缓存
有时,缓存真的让人头痛,它带来了很多问题,尤其是开发和生产之间的不同行为。那么你可能想要完全禁用缓存。我们可以设置 const revalidate = 0
或 const dynamic = 'force-dynamic'
来实现这一点。
javascript
export default async function Home() {
const posts = (await (
await fetch('https://jsonplaceholder.typicode.com/posts')
).json()) as Post[];
return (
<main className={styles.main}>
<ol>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/post/${post.id}`}>
{post.id}. {post.body}
</Link>
</li>
))}
</ol>
</main>
);
}
export const revalidate = 0;
export const dynamic = 'force-dynamic';
这完全不是建议的做法。没有缓存,你可能会遇到性能问题。即使你的页面非常动态,我个人建议给它设置一个小的 revalidate 时间。
总结
我们讨论了 4 种不同的 NextJs 缓存事项,希望你现在对它有了更好的理解。再次强调,对于 NextJs,你必须在本地测试生产构建,以确保它具有你想要的行为。由于缓存系统,开发构建和生产构建可能会有不同的行为。