如何为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
相关产品推荐
相关产品推荐

