跳到主要内容

Next.js 缓存与重验证

从 13 开始,缓存已经不是 Next.js 的边角能力了。到了 15 和 16,这一层几乎成了框架心智的核心部分。

理解 Next 的缓存,最实用的方式不是死记 API,而是先分清楚三件事:

  • 哪些内容可以缓存
  • 缓存放在哪一层
  • 数据更新后,谁来负责失效

为什么这块很重要

在普通 React 项目里,很多人把缓存理解成“性能优化”。

在 Next.js 里,缓存还会直接影响:

  • 页面是不是静态的
  • 路由切换时会不会复用结果
  • 写操作之后页面能不能立刻看到新数据
  • 首屏速度和请求数量

所以这块不是附属知识,而是基础能力。

最先记住的三个入口

cache

await fetch('https://api.example.com/products', {
cache: 'force-cache',
});

await fetch('https://api.example.com/products', {
cache: 'no-store',
});

next.revalidate

await fetch('https://api.example.com/products', {
next: { revalidate: 300 },
});

next.tags

await fetch('https://api.example.com/products', {
next: { tags: ['products'] },
});

这三组已经足够覆盖大部分日常使用场景。

几种最常见的缓存策略

1. 强缓存倾向

await fetch(url, { cache: 'force-cache' });

适合:

  • 变化不频繁的数据
  • 文档、配置、分类、基础字典等内容

2. 完全不缓存

await fetch(url, { cache: 'no-store' });

适合:

  • 实时数据
  • 与当前用户强相关且必须最新的数据
  • 后台列表、订单状态、权限结果等

3. 定时重验证

await fetch(url, {
next: { revalidate: 60 },
});

适合:

  • 不需要每次请求都实时更新
  • 但也不能一直停留在构建时结果

标签式重验证

这是 App Router 非常值得掌握的一块。

await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
});

当数据写入发生后,可以按标签失效:

'use server';

import { revalidateTag } from 'next/cache';

export async function createPost(data: FormData) {
await db.post.create({
title: String(data.get('title')),
});

revalidateTag('posts');
}

这样比“整页粗暴刷新”更细,也更适合复杂应用。

路径级重验证

'use server';

import { revalidatePath } from 'next/cache';

export async function updateProfile() {
await db.user.update();
revalidatePath('/settings/profile');
}

适合:

  • 只需要刷新某一页
  • 逻辑上更偏页面级,而不是数据资源级

16 之后更该怎么理解缓存

较新的官方路线里,缓存模型越来越强调“显式声明”。

这意味着:

  • 不要依赖隐式默认值去猜页面行为
  • 请求缓存、路由缓存、数据标签、重验证入口最好明确写出来
  • 写操作和读操作最好从一开始就想好失效方式

Cache Components

16 把 cacheComponents 放到了更重要的位置。

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
cacheComponents: true,
};

export default nextConfig;

这条线代表的方向很明确:缓存不再只是针对请求,而是逐步进入组件级和渲染级模型。

常见误区

1. 把缓存当成“以后再优化”

到了 Next 15 / 16,这种想法风险很高。很多线上表现问题一开始就是缓存策略没想清楚。

2. 只会 revalidatePath

能用,但不够细。项目复杂后,标签式重验证通常更灵活。

3. 看到数据旧了就直接 no-store

这会简单,但也容易把性能和扩展性一起丢掉。更好的方式通常是先判断:

  • 真的是强实时吗
  • 能不能接受几秒或几十秒延迟
  • 能不能通过标签失效在写后立即刷新

一个比较稳的思考顺序

  1. 这份数据是不是必须每次实时
  2. 如果不是,多久更新一次能接受
  3. 写操作后需要影响哪些页面或资源
  4. 用路径失效还是标签失效更合适

推荐继续往下看

  1. Server Actions
  2. 升级检查清单

参考资料