前提:canary 但是 config 里面注释掉 ppr = 正式版
1.
> 我看到一个 noStore() 标注的方法执行的时候,别的 API 方法也跟着被调用,但是作为 cache fallback 的方法不被调用。我觉得这个就是 ppr 了对吗? canary 的支持只不过让这个实现更加简单了一点而已:不需要 cache 方法包裹,而是没有使用 noStore 就默认为 cache 。
ppr 指的是部分预渲染,相当于默认一切都是静态的,只要 suspense 里面没有 no store,但是这里有坑,最后说。
在正式版/canary without ppr 中,
这个 noStore 是不是 cache 的 callback,都没有关系,只要你这个路线中,任意一个地方出现了 noStore,next 在 build 的时候,就等价于路由段配置中的 force-dynamic 或者 fetch 中的 no-store,相当于退出静态渲染,改用动态。
当你刷新的时候,为什么另外两个组件都去获取数据了,有 cache 的却没反应呢,因为 unstable_cache 它 cache 的函数的返回值。
所以这跟 ppr 有什么关系?重要的是要把 ppr 和 cache 分开。
你加 cache,只是为了优化,降低它查询数据库的频率。
2.
有一点非常值得注意的是,在 nextjs 中的 fetch,和 unstale_cache 表现是不一样的,
fetch 缓存的是 fetch 请求的返回值,也就是说,为什么在没有 unstable_cache 的时候,你会看到你写的测试时间的方法被调用,因为整个请求数据+计算时间的函数都会被执行,只不过 await sql`` 的时候,直接从缓存中给你值了(这里也有坑)
而 unstable_cache 缓存的是给定函数的返回值,(请求数据+计算时间)这个函数的返回值被缓存了,里面的计算时间自然不会被执行了。
3.
> 但是我发现,如果页面是 static 的,肯定就不会有问题。但是我猜大概从表现的危害上来说也可以接受吧,毕竟页面都是 dynamic 的,让它每点一下就调一次又怎么样了呢,但是原因还是不清楚。
static 的时候,直接就是 html 了,肯定不会调用你写的计时方法。
你需要确保的是,如果函数都加上 unstable_cache 了,你这个问题应该就不会出现了吧。
4.
> 关于第 5 点,我不知道你是怎么测试的,但是我测试的情况下,如果 API 方法里使用了 noStore ,然后把这个方法作为 cache 的 fallback 的话,确实不会退出静态生成。build 的时候显示的 tree 也显示页面仍然是 static ,
你这个是在什么条件下测试的?
> 如果不用 cache 的话,tree 马上变为动态 λ 的了。
在正式版中,任意组件任意位置存在 noStore ,是的,路线就会退出静态渲染,跟 cache 没什么关系。
> 我感觉正式版里只要用 cache 了,noStore 就跟没有调用一样,好像 cache 这个缓存,会直接接管这整个方法调用的结果,然后选择是否缓存。
正如我前面所说,这事儿跟 cache 没什么关系,cache 只是为了降低 动态组件 查询数据库的频率,在静态路线里写不写 cache 没什么意义,是同一个结果
5.一些坑以及一些猜测
深入理解 nextjs 的缓存,
我 fork 了你的项目,RevenueChart 没有 cache,另外两个有,build,start
当你刷新页面的时候,你会发现 RevenueChart 在获取数据,你的终端上出现 fetching data,另外两个数据立刻就有了
这中间发生了什么?
> unstable_noStore can be used to declaratively opt out of static rendering and indicate a particular component should not be cached.
> unstable_noStore 优于 export const dynamic = 'force-dynamic' ,因为它更细粒度并且可以在每个组件的基础上使用
unstable_noStore 是用来配合部分预渲染,实现细粒度的控制组件的渲染方式,当你在另外两个组件中声明 noStore 的时候,就成了一个动态组件
> unstable_noStore is equivalent to cache: 'no-store' on a fetch
> no-store - Next.js 在每次请求时从远程服务器获取资源,而不查看缓存,并且不会使用下载的资源更新缓存。
但是这并意味着我们就没有 data cache 了
> Next.js 有一个内置的数据缓存(data cache),可以在传入的服务器请求和部署中保留数据获取的结果。这是可能的,因为 Next.js 扩展了本机 fetch API 以允许服务器上的每个请求设置自己的持久缓存语义。
---以下为猜测---
假如我们的组件声明了 noStore,组件内部 fetch 了一个资源,没有理由这个 fetch 会退出 nextjs 的 data cache.
我们声明了 noStore,只表达了这是个动态组件,它不是网页静态的一部分,当你访问/刷新网页的时候,你应该永远从服务器获取组件的内容(这里先不提客户端缓存)
因为 @
vervel/postgre 的 sql 也是基于 fetch 的
这一点很奇怪,完全想不通,但是参考
https://github.com/orgs/vercel/discussions/4696https://nextjs.org/docs/messages/ppr-caught-error> Database Error: NeonDbError: Error connecting to database: Route /dashboard needs to bail out of prerendering at this point because it used revalidate: 0. React throws this special object to indicate where. It should not be caught by your own try/catch. Learn more:
https://nextjs.org/docs/messages/ppr-caught-error> As a convenience, it is not necessary to set the cache option if revalidate is set to a number since 0 implies cache: 'no-store' and a positive value implies cache: 'force-cache'.
> 为方便起见,如果 revalidate 设置为数字,则无需设置 cache 选项,因为 0 隐含 cache: 'no-store' 且正值意味着 cache: 'force-cache'
sql 的内部实现可能用的是 fetch(``, { next: { revalidate: 0 } })
一个悲伤的事情是,正式版的静态渲染和 revalidate 0 配合工作良好,ppr 却不行,这也就意味着,对于带有数据库查询的组件,你并不能 ppr 它们变成完全静态的
当然,这个问题是可以被解决的,配合 router handle,你其实可以用 fetch 获取某些数据库的信息(安全吗?)
---接下来让我们回到刷新后的渲染过程---
为了解决 sql 的 revalidate0,我们引入了 unstable_cache 。
当我们刷新页面的时候,服务器上已经有了那两个组件 cache 的 RSCP,直接就发送过来了,内容是立即出现的
而 RevenueChart 没有 cache,并且由于 sql 的 revalidate0 ,需要重新查询数据库,终端上出现 fetching data
为什么第一次慢后面快?所以给你一种查询没有发出去的错觉?
猜测 1:可能确实没发出去,毕竟 revalidate0 是我的猜测
猜测 2:发出去了,第一次慢是因为网络问题,vercel 在国外,tcp 慢启动,第二次第三次就快了
这一点其实也很好验证,你一边刷新(可以用插件自动刷新),一边在 vercel 数据库中新建一个数据就可以判断了,交给你了,等你反馈
---客户端缓存/路由器缓存---
> 当用户在路线之间导航时,Next.js 会缓存访问过的路线段并预取用户可能导航到的路线(基于视口中的 <Link> 组件)。
> 导航之间不会重新加载整页,并且会保留 React 状态和浏览器状态。
> 会话:缓存在整个导航过程中持续存在。但是,它会在页面刷新时被清除
这一点也很好验证,当你刷新的时候,RevenueChart 会重新获取数据,当你点击左边导航随便一个再点回来,不会触发重新获取,终端上也不会有 fetching data
---最后---
目前你这个例子有一点小,还都是获取 db ,可能无法明显看出 ppr 的优势
对于 ppr 和 sql revalidate 0 的问题,如果真的有想要完全静态的组件还带有 db 查询,
我的建议是 unstable_cache 梭哈,不设置过期时间,算是一种半静态吧,除了第一次比较慢,后面其他用户第二次访问就很快了