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

如何为Next.js API路由设置有效响应缓存?

解决Next.js API路由的响应缓存问题

针对你的场景(新闻每月更新,需缓存翻译后的响应),可以从以下几个方向优化缓存策略:

1. 修复Cache-Control配置并利用平台缓存

你之前设置的s-maxage=3600仅1小时,且s-maxage是给CDN/边缘缓存使用的(比如Vercel的Edge Network),本地开发环境不会触发该缓存。结合新闻每月更新的特性,建议调整缓存时长:

res.setHeader(
  "Cache-Control",
  "public, s-maxage=2592000, stale-while-revalidate=86400"
);
  • s-maxage=2592000:让CDN缓存内容30天,刚好匹配新闻更新周期
  • stale-while-revalidate=86400:缓存过期后,先返回旧数据给用户,后台异步重新生成新缓存,避免用户等待

注意:该配置在本地开发时不会生效,需部署到支持边缘缓存的平台(如Vercel)才能看到效果。部署后首次请求会执行完整流程,后续请求直接返回CDN缓存结果,响应时间会大幅降低。

2. 服务器端内存缓存(适合自托管场景)

如果你的Next.js是自托管状态(无CDN支持),可以用内存缓存库(如node-cache)在服务器层面缓存结果:

步骤1:安装依赖

npm install node-cache

步骤2:修改API路由代码

import NodeCache from 'node-cache';
import _ from 'lodash';

// 创建缓存实例,设置过期时间30天
const cache = new NodeCache({ stdTTL: 2592000 });

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // 先检查缓存是否存在
  const cachedData = cache.get('translated_news');
  if (cachedData) {
    return res.status(200).json(cachedData);
  }

  try {
    // 原有的新闻获取逻辑
    const post = await fetch("https://gatenine.ch/wp-json/wp/v2/posts")
      .then((res) => res.json())
      .then((data) =>
        data.map((item: any) =>
          _.pick(
            item,
            "yoast_head_json",
            "title",
            "description",
            "modified",
            "content"
          )
        )
      );

    // 翻译逻辑
    const newPost = await Promise.all(
      post.map(async (element: any) => {
        const fr = await translate(
          [element.title.rendered, element.content.rendered],
          { from: "de", to: "fr" }
        ).catch((err) => {
          console.log(err);
          return null;
        });

        return _.extend(element, {
          frTitle: fr ? fr[0] : element.title.rendered,
          frContent: fr ? fr[1] : element.content.rendered
        });
      })
    );

    const responseData = { message: "news successfully retrieved", data: newPost };
    // 将结果存入缓存
    cache.set('translated_news', responseData);
    
    return res.status(200).json(responseData);
  } catch (e) {
    console.log("请求/翻译失败", e);
    return res.status(500).json({ message: "获取新闻失败" });
  }
}

该方案在服务器运行期间会把结果存在内存中,后续请求直接读取缓存,无需重复调用第三方接口和翻译服务。

3. 文件系统缓存(持久化缓存)

如果服务器重启后不想丢失缓存,可以把结果写入本地文件:

import fs from 'fs/promises';
import path from 'path';
import _ from 'lodash';

const CACHE_FILE = path.join(process.cwd(), 'cache', 'news-translated.json');

// 确保缓存目录存在
async function ensureCacheDir() {
  try {
    await fs.access(path.dirname(CACHE_FILE));
  } catch {
    await fs.mkdir(path.dirname(CACHE_FILE), { recursive: true });
  }
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  await ensureCacheDir();
  
  // 检查缓存文件是否存在且未过期(30天)
  try {
    const stats = await fs.stat(CACHE_FILE);
    const now = Date.now();
    if (now - stats.mtime.getTime() < 2592000 * 1000) {
      const cachedData = await fs.readFile(CACHE_FILE, 'utf-8');
      return res.status(200).json(JSON.parse(cachedData));
    }
  } catch {}

  // 原有的新闻获取与翻译逻辑
  try {
    const post = await fetch("https://gatenine.ch/wp-json/wp/v2/posts")
      .then((res) => res.json())
      .then((data) =>
        data.map((item: any) =>
          _.pick(
            item,
            "yoast_head_json",
            "title",
            "description",
            "modified",
            "content"
          )
        )
      );

    const newPost = await Promise.all(
      post.map(async (element: any) => {
        const fr = await translate(
          [element.title.rendered, element.content.rendered],
          { from: "de", to: "fr" }
        ).catch((err) => {
          console.log(err);
          return null;
        });

        return _.extend(element, {
          frTitle: fr ? fr[0] : element.title.rendered,
          frContent: fr ? fr[1] : element.content.rendered
        });
      })
    );

    const responseData = { message: "news successfully retrieved", data: newPost };
    // 将结果写入缓存文件
    await fs.writeFile(CACHE_FILE, JSON.stringify(responseData));
    
    return res.status(200).json(responseData);
  } catch (e) {
    console.log("请求/翻译失败", e);
    return res.status(500).json({ message: "获取新闻失败" });
  }
}

这种方式缓存会持久化到文件,服务器重启后依然有效,适合需要长期缓存且无CDN的场景。

关键注意点

  • 若使用Vercel部署,优先选择Cache-Control + 边缘缓存,这是最简单高效的方案
  • 内存缓存适合短期运行的服务器,重启后缓存会清空
  • 文件缓存适合需要持久化缓存的场景,但要注意定期清理过期文件
  • 可结合新闻的modified字段,仅在原新闻更新时重新翻译,进一步优化缓存策略

内容的提问来源于stack exchange,提问作者L.DZ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 02:53:22