Express临时存储数据返回功能在Vercel部署后失效如何解决?
问题原因及解决方案
核心原因
Vercel 采用无服务器(Serverless)函数架构,和本地长期运行的 Express 服务器逻辑完全不同:
- 你代码里
app.listen()中的初始化逻辑不会在 Vercel 环境执行——Vercel 不会让应用持续监听端口,而是将 Express 包装成按需触发的 serverless 函数,只有请求到来时才启动实例处理。 - 即使某实例执行了缓存初始化,请求结束后实例可能被销毁,下次请求会启动全新实例,内存缓存无法跨请求/实例共享,自然返回空对象
{}。
解决方案
方案一:请求触发时初始化内存缓存(低成本,适合低频更新场景)
将缓存初始化逻辑从app.listen()移到请求中间件中,确保每个实例启动时都会初始化缓存,同一实例内的请求可复用缓存:
修改index.js:
import express from 'express'; import cors from 'cors'; import morgan from 'morgan'; import { routes } from '../routes'; export let cache = {}; let isCacheInitialized = false; // 封装缓存初始化逻辑 const initCache = async () => { if (!isCacheInitialized) { const result = await someFunc(); // 如果someFunc是同步函数,去掉await cache = result; isCacheInitialized = true; } }; const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.static('public')); app.use(cors()); app.use(express.json()); app.use(morgan('tiny')); // 在路由处理前初始化缓存 app.use(async (req, res, next) => { await initCache(); next(); }); app.use('/', routes); app.disable('x-powered-by'); // Vercel 需要导出 app,而非调用 listen export default app;
router.js保持原有逻辑即可。
注意:冷启动请求会触发初始化,后续同一实例的请求复用缓存;若实例被销毁,下次请求会重新初始化,适合数据更新不频繁的场景。
方案二:使用外部缓存存储(高可靠,适合跨实例共享场景)
用 Redis 替代内存缓存,Vercel 官方提供 Redis 集成,可实现跨实例的缓存共享与持久化:
- 安装 Redis 客户端:
npm install ioredis - 在 Vercel 控制台启用 Redis 服务,获取
REDIS_URL环境变量 - 修改代码:
index.js:
import express from 'express'; import cors from 'cors'; import morgan from 'morgan'; import { routes } from '../routes'; import Redis from 'ioredis'; const redis = new Redis(process.env.REDIS_URL); // 封装缓存读取/初始化逻辑 export const getCache = async () => { const cachedStr = await redis.get('app-cache'); if (cachedStr) { return JSON.parse(cachedStr); } // 无缓存时重新获取数据 const result = await someFunc(); // 设置缓存过期时间(示例为1小时) await redis.set('app-cache', JSON.stringify(result), 'EX', 3600); return result; }; const app = express(); // 中间件部分不变 app.use('/', routes); app.disable('x-powered-by'); export default app;
router.js:
import { getCache } from '../index.js'; routes.get( '/cache', catchAsync(async (req: Request, res: Response) => { const cache = await getCache(); const query = req.query?.fields || ''; const fields = query ? String(query).split(',') : []; res.status(200).json(fields.length ? pick(cache, fields) : cache); }), );
方案三:构建阶段生成静态缓存文件(高性能,适合静态数据场景)
如果someFunc()的数据在构建阶段就能确定,可在 Vercel 构建时生成静态 JSON 文件,路由直接读取:
- 在
package.json中添加构建脚本:
{ "scripts": { "vercel-build": "node scripts/generate-cache.js" } }
- 创建
scripts/generate-cache.js:
const fs = require('fs'); const someFunc = require('../path/to/someFunc'); async function generateCache() { const result = await someFunc(); fs.writeFileSync('./public/cache.json', JSON.stringify(result)); } generateCache();
- 修改
router.js读取静态文件:
import fs from 'fs/promises'; import path from 'path'; routes.get( '/cache', catchAsync(async (req: Request, res: Response) => { const cachePath = path.join(process.cwd(), 'public', 'cache.json'); const cacheStr = await fs.readFile(cachePath, 'utf8'); const cache = JSON.parse(cacheStr); const query = req.query?.fields || ''; const fields = query ? String(query).split(',') : []; res.status(200).json(fields.length ? pick(cache, fields) : cache); }), );
场景选择
- 方案一:数据更新频率低、可接受冷启动延迟,优先选
- 方案二:需要跨实例共享缓存、数据需定期更新,选这个
- 方案三:数据在构建时固定不变,性能最优
内容的提问来源于stack exchange,提问作者Bikas Lin
相关产品推荐
相关产品推荐

