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

在Next.js App Router中如何缓存MongoDB查询?

在Next.js App Router中如何缓存MongoDB查询?

嘿,这个问题我太熟了——高并发下每次请求都新建MongoDB连接、重复查询,确实会把数据库给冲得够呛!先聊聊你想的数组缓存方案,再给你几个更适配Next.js场景的靠谱选择:

首先说你的数组方案:思路是对的,但直接用普通数组有几个明显的坑:

  • 数组查找效率低,每次都要遍历找对应的slug,数据多了会变慢;
  • 没有过期机制,你更新文章后,用户看到的还是旧内容,除非重启服务器;
  • Next.js在Vercel这类无服务器平台部署时,每个实例都是独立的,数组缓存只能在单个实例内生效,多实例的话还是会有重复查询;
  • 数组会无限膨胀,缓存的文章越多占用内存越高,搞不好会触发内存溢出。

所以更推荐下面几种方案:

1. 用Next.js自带的数据缓存(最推荐,零额外成本)

Next.js App Router的服务器组件默认就会缓存数据请求!也就是说,只要你的getArticleBySlug是在服务器组件(比如app/[slug]/page.js)里调用的,Next.js会自动缓存第一次查询的结果,后续请求直接复用缓存,根本不会再去连数据库。

你只需要注意两点:

  • 把dbConnect改成单例模式,避免每次请求新建MongoDB连接:
    let cachedDb = null;
    
    async function dbConnect() {
      if (cachedDb) {
        return cachedDb;
      }
      const db = await mongoose.connect(process.env.MONGODB_URI);
      cachedDb = db;
      return db;
    }
    
    这样每个服务器实例只会建立一次MongoDB连接,所有请求复用这个连接,直接解决连接数爆炸的问题。
  • 配置缓存过期时间(可选):如果你希望文章更新后,用户能在一定时间内看到新内容,可以在路由组件里加revalidate配置,比如:
    // app/[slug]/page.js
    export const revalidate = 60; // 每60秒重新验证一次缓存
    
    export default async function ArticlePage({ params }) {
      const article = await getArticleBySlug(params.slug);
      // ...渲染文章
    }
    
    这样Next.js会每60秒自动重新查询一次数据库,更新缓存,平衡了缓存效率和内容新鲜度。

2. 改进版内存缓存(适合单实例部署)

如果你还是想自己控制缓存逻辑,别用数组,改用Map结构来存,还加上过期时间:

const articleCache = new Map();
const CACHE_TTL = 60 * 60 * 1000; // 缓存1小时

export async function getArticleBySlug(slug) {
  // 先查缓存
  const cachedEntry = articleCache.get(slug);
  if (cachedEntry && Date.now() - cachedEntry.timestamp < CACHE_TTL) {
    return cachedEntry.data;
  }

  try {
    await dbConnect();
    const article = await Article.findOne({ slug })
      .select("-updatedAt -__v -_id -image192URL -image296URL -image566URL -image624URL -image80URL");
    if (!article) {
      return null;
    }
    // 存入缓存
    articleCache.set(slug, {
      data: article,
      timestamp: Date.now()
    });
    return article;
  } catch (error) {
    throw new Error("db error");
  }
}

这个方案比数组好太多:Map的键值对查找快,有过期时间能自动淘汰旧缓存,还能避免内存无限膨胀。但还是要注意,它只能在单个服务器实例内生效,多实例部署的话还是会有重复缓存。

3. Redis分布式缓存(适合多实例/大规模场景)

如果你的博客流量很大,用了多台服务器或者Vercel的多个边缘实例,内存缓存就不够用了——因为每个实例的缓存是独立的,还是会有重复查询。这时候用Redis分布式缓存就最合适,所有实例共享同一个缓存池。

代码大概是这样的:

import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);
const CACHE_TTL = 3600; // 缓存1小时

export async function getArticleBySlug(slug) {
  const cacheKey = `article:${slug}`;
  // 先查Redis缓存
  const cachedArticle = await redis.get(cacheKey);
  if (cachedArticle) {
    return JSON.parse(cachedArticle);
  }

  try {
    await dbConnect();
    const article = await Article.findOne({ slug })
      .select("-updatedAt -__v -_id -image192URL -image296URL -image566URL -image624URL -image80URL");
    if (!article) {
      return null;
    }
    // 存入Redis,设置过期时间
    await redis.set(cacheKey, JSON.stringify(article), 'EX', CACHE_TTL);
    return article;
  } catch (error) {
    throw new Error("db error");
  }
}

Redis方案能彻底解决多实例下的重复查询问题,缓存命中率会非常高,数据库连接数能降到极少。

总结一下优先级:

  • 如果你用Vercel部署Next.js,优先用Next.js自带的缓存+单例DB连接,零额外成本,效果拉满;
  • 单服务器实例部署的话,用改进版Map内存缓存也可以;
  • 大规模多实例场景,上Redis分布式缓存。

备注:内容来源于stack exchange,提问作者Milán Egri

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 13:43:03