You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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())

核心疑问

  1. 为何cache()不遵守revalidate规则?
  2. Prisma查询结合cache() + next: { revalidate }是否有已知限制?
  3. 如何调试缓存键是否生成正确?

环境版本

  • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 02:53:16