Next.js 16中cache()函数为何仍每次请求都访问数据库?
Next.js 16 + React cache() 缓存失效问题排查与解决
问题背景
我正在使用Next.js 16结合React的cache()函数,尝试在布局和页面间实现数据库查询的去重与跨请求缓存,但缓存未按预期生效。
代码示例
lib/data.ts
import { cache } from 'react'; import db from '@/lib/db'; export const getUser = cache(async (userId: string) => { console.log('Fetching user from DB...', userId); return await db.user.findUnique({ where: { id: userId }, next: { revalidate: 3600, tags: ['user'] } // 期望1小时缓存 }); });
app/layout.tsx
import { getUser } from '@/lib/data'; export default async function RootLayout({ children }) { const user = await getUser('123'); // 预期被缓存 return ( <html> <body> <Header user={user} /> {children} </body> </html> ); }
app/dashboard/page.tsx
import { getUser } from '@/lib/data'; export default async function DashboardPage() { const user = await getUser('123'); // 相同ID,预期复用缓存 return <Dashboard user={user} />; }
预期vs实际行为
- 预期:首次请求访问数据库并打印日志,1小时内后续请求从缓存获取,不再打印日志
- 实际:每次请求都打印
Fetching user from DB...,缓存完全失效
已尝试操作
- 确认Prisma + Next.js支持数据缓存配置
- 验证
cache()从react正确导入 - 未使用
unstable_cache或no-store - 在生产模式(
next start)下测试 - 调用链中无动态函数(如
cookies())
核心疑问
- 为何
cache()不遵守revalidate规则? - Prisma查询结合
cache()+next: { revalidate }是否有已知限制? - 如何调试缓存键是否生成正确?
环境版本
- Next.js: 16.0.0
- React: 19.0.0
- Prisma: 5.15.0
问题原因与解决方案
1. 核心误解:React cache()的作用范围
React的cache()函数仅实现同请求内的调用去重(比如同一次页面渲染中,布局和页面都调用getUser('123'),只会执行一次查询),但它是内存级临时缓存,请求结束后就会释放,无法实现跨请求的持久化缓存(即你期望的1小时缓存)。
另外,Prisma查询中的next: { revalidate }配置是无效的——这个配置是Next.js为fetch或自身缓存API设计的,Prisma不会识别或处理该参数。
2. 正确实现跨请求持久化缓存
要实现跨请求的1小时缓存,需要使用Next.js的unstable_cache(稳定后会改为cache)来包裹数据获取逻辑,让Next.js处理持久化缓存:
修改lib/data.ts:
import { unstable_cache } from 'next/cache'; import db from '@/lib/db'; // 使用Next.js的不稳定缓存API实现持久化缓存 export const getUser = unstable_cache( async (userId: string) => { console.log('Fetching user from DB...', userId); return await db.user.findUnique({ where: { id: userId }, }); }, ['user'], // 缓存键前缀,结合参数自动生成唯一键 { revalidate: 3600, tags: ['user'] } // 1小时缓存,设置缓存标签 );
如果需要同时保留同请求内的去重,可以结合React的cache()和Next.js的unstable_cache:
import { cache } from 'react'; import { unstable_cache } from 'next/cache'; import db from '@/lib/db'; const _getUser = unstable_cache( async (userId: string) => { console.log('Fetching user from DB...', userId); return await db.user.findUnique({ where: { id: userId } }); }, ['user'], { revalidate: 3600, tags: ['user'] } ); export const getUser = cache(_getUser);
3. 调试缓存键的方法
- 手动打印缓存键:在缓存函数中,根据参数生成并打印预期的缓存键,确认相同参数生成相同键:
export const getUser = unstable_cache( async (userId: string) => { const cacheKey = `user-${userId}`; console.log('Cache key:', cacheKey); // ... 查询逻辑 }, ['user'], { revalidate: 3600 } ); - 查看Next.js缓存目录:生产模式下,Next.js会将缓存存储在
.next/cache目录中,你可以查看其中的条目是否符合预期(注意:该目录结构可能随版本变化)。 - 使用缓存标签验证:调用
revalidateTag('user')手动触发缓存刷新,观察是否重新查询数据库,以此验证缓存是否生效。
内容的提问来源于stack exchange,提问作者Mina Golzari Dalir
相关产品推荐
相关产品推荐

