@ fortedigital/ nextjs-cache- handler (中文(简体) )
最初建在顶部的缓存工具 @neshca/cache-handler,为专门使用的案件提供额外的缓存处理器,重点是基于Redis的缓存.
从版本开始 2.0.0此软件包不再依赖于 @neshca/cache-handler 并与Next.js 15和部分16保持并兼容。 见 兼容矩阵 详细特性支持.
版本要求 :
- 下一个.js 15: 2.0.0+版本(3.0.0+版本建议最新改进和维护开发)
- 下一个.js 16: 3.0.0+ 需要的版本
目录
文档
文件编号: @neshca/cache-handler - caching-tools.github.io/next-shared-cache 页面存档备份,存于互联网档案馆 (简体中文) 虽然一些细节可能已经过时,但大部分内容仍然相关。 下文介绍新的特征或相关变化.
移徙
从 @neshca/cache-handler
如果您已经使用 @neshca/cache-handler 设置非常精简,你只需要替换软件包的引用。 如果你开始新鲜,请检查 实例项目。 。 。 .
缓存处理器
在此之前:
// cache-handler.mjs
import { CacheHandler } from "@neshca/cache-handler";
CacheHandler.onCreation(() => {
// setup
});
export default CacheHandler;
之后:
// cache-handler.mjs
import { CacheHandler } from "@fortedigital/nextjs-cache-handler";
CacheHandler.onCreation(() => {
// setup
});
export default CacheHandler;
仪器
在此之前:
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { registerInitialCache } =
await import("@neshca/cache-handler/instrumentation");
const CacheHandler = (await import("../cache-handler.mjs")).default;
await registerInitialCache(CacheHandler);
}
}
之后:
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { registerInitialCache } =
await import("@fortedigital/nextjs-cache-handler/instrumentation");
const CacheHandler = (await import("../cache-handler.mjs")).default;
await registerInitialCache(CacheHandler);
}
}
先决条件
在安装前,请确保您有:
- 节点.js >= 22.0.0
- 下一个.js qq 15.2.4 (为 2.0.0+版)或 qq 16.0.0 (为 3.0.0+版)
- 重写 >= 5.5.6 (或相容再相容服务)
- 小时 9.0.0(促进发展)
见 版本要求 用于软件包版本相容性.
安装
npm i @fortedigital/nextjs-cache-handler
如果升级 从下14或更早, 冲出您的 Redis 缓存 在本地运行新版本应用程序之前,并在您主机环境中运行. 下 14 和 15 之间的缓存格式不兼容。 。 。 .
快速启动
这里有一个最小的设置开始:
// cache-handler.mjs
import { CacheHandler } from "@fortedigital/nextjs-cache-handler";
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
import { createClient } from "redis";
const client = createClient({ url: process.env.REDIS_URL });
await client.connect();
CacheHandler.onCreation(() => ({
handlers: [createRedisHandler({ client })],
}));
export default CacheHandler;
然后在您体内配置 next.config.js数字 :
// next.config.js
module.exports = {
cacheHandler: require.resolve("./cache-handler.mjs"),
};
关于错误处理、倒置和生产设置的完整实例,见 实例 下节。 快速启动代码并非用于生产.
下一个.js 兼容性
原画 @neshca/cache-handler 软件包不支持 Next.js 15.
在 2.0.0 之前,这个包包提供了包装和增强,允许使用 @neshca/cache-handler 与下册.js 15.
从2.0版开始, @fortedigital/nextjs-cache-handler 是一个独立的解决办法,不依赖 @neshca/cache-handler 并完全兼容 Next.js 15和 复数 5。 。 。 .
版本要求 :
- 下一个.js 15: 2.0.0+版本(3.0.0+版本建议最新改进和维护开发)
- 下一个.js 16: 3.0.0+ 需要的版本
我们的目标是跟上新的Next.js版本,并将采用带有适当版本起伏的重大改动.
[尖叫声] 下一条,js 16.3.0 断裂变化: 从Next.js 16.3.0开始,预先请求应用路由器的路由树(
/_tree需要每个区块的RSC有效载荷(segmentData在缓存条目中存在。 此软件包的版本在 3.3.0 (单位:千美元) 没有填充segmentData当从磁盘恢复初始缓存时(通过)registerInitialCache)),这导致"Next.js 16.3.0+"客户端收到了无法解析的响应,用于/_tree预选请求和无限重试.固定于3.3.0. 如果你在Next.js 16.3.0上或稍后,升级到
@fortedigital/nextjs-cache-handler3.3.0+ (中文(简体) ).
[尖叫声] Next.js 16.3.8+路由扫描缓存密钥: 从Next.js 16.3.8(和 16.4.x)开始,响应缓存条目(
APP_PAGE, (中文(简体) ),APP_ROUTE, (中文(简体) ),PAGES)储存在路由范围键下(/route-cache/<kind>/<hash>/$<path>)而不是赤出道名. 此软件包的版本在 3.4.0 (中文(简体) ) 种子初始缓存项(通过registerInitialCache)在赤色路径名下,所以Next.js从不读取,并打破了Pages路由器revalidatePath无效.固定于 3. 4. 0. 如果你在Next.js 16.3.8或以后,升级到
@fortedigital/nextjs-cache-handler3.4.0+. 旧路径名密钥下存储的条目在升级后不再被读取 Next.js 并自行过期.
特性相容性矩阵
| 特性 | 下一个.js 15 | 下一个.js 16 | 页:1 |
|---|---|---|---|
| 获取 API 缓存 | |||
fetch 默认缓存( e)force-cache(中文(简体) ) | ✅ | ✅ | 默认行为, 无限缓存 |
fetch 与 no-store | ✅ | ✅ | 从不缓存, 总是新鲜的 |
fetch 与 no-cache | ✅ | ✅ | 在每个请求上验证缓存 |
fetch 与 next.revalidate | ✅ | ✅ | 基于时间的重新验证 |
fetch 与 next.tags | ✅ | ✅ | 基于标记的缓存无效 |
| 快取无效 | |||
revalidateTag(tag) | ✅ | 不详 | Next.js 16的突破变化 |
revalidateTag(tag, cacheLife) | 不详 | ✅ | Next.js 中新需要的 API 16 |
updateTag(tag) | 不详 | ✅ | 服务器动作中立即无效的新 API |
revalidatePath(path) | ✅ | ✅ | 基于路径的再验证 |
revalidatePath(path, type) | ✅ | ✅ | 类型特定路径重新验证 |
| 函数缓存 | |||
unstable_cache() | ✅ | ✅ | 缓存任何带有标签并重新验证的函数 |
| 静态生成 | |||
generateStaticParams() | ✅ | ✅ | 静态参数生成 |
| ISR(递增静态再生) | ✅ | ✅ | 按需再生 |
路由段配置( N)revalidate, (中文(简体) ), dynamic页:1 | ✅ | ✅ | 所有段配置选项 |
| Redis 客户端支持 | |||
redis 软件包(节点- redis) | ✅ | ✅ | 官方 Redis 客户端 - 完全支持 |
ioredis 软件包 | ✅ | ✅ | IORedis 客户端 - 完全支持 |
| 下一个.js 16 新地物 | |||
cacheHandlers 配置(用于) 'use cache'(中文(简体) ) | ❌ | ❌ | 尚未支持 - 需要帮助 |
'use cache' 指令 | ❌ | ❌ | 尚未支持 - 需要帮助 |
'use cache: remote' 指令 | ❌ | ❌ | 尚未支持 - 需要帮助 |
'use cache: private' 指令 | ❌ | ❌ | 尚未支持 - 需要帮助 |
cacheComponents | ❌ | ❌ | 尚未支持 - 需要帮助 |
注释:
revalidateTag()下一个.js 16 需要一个cacheLife参数('max', (中文(简体) ),'hours',或'days') (中文(简体) ). 这是一个突破性的变化 从Next.js 15.cacheLife剖面图主要是为Vercel的基础设施而设计的. 自定义缓存处理器可能无法完全区分不同的cacheLife简介.updateTag()仅在服务器动作中可用,而不是路由处理器.- 新的
cacheHandlersAPI 和'use cache'指令尚未为本套件所支持.
处理器
redis-strings
一个基于 Redis 的处理器,用于基于密钥和标记的缓存. 与原始执行相比,它通过执行TTL-捆绑的hashmaps来防止由正在增长的共享标记图所引发的内存泄露.
特征 :
- 使用密钥过期
EXAT或者说EXPIREAT - 基于标记的再验证
- 自动 TTL 管理
- Next.js 15+相容性的自动缓冲/字符串转换(以前需要)
buffer-string-decorator在1.x.x版本中 - 默认
revalidateTagQuerySize数字 :10_000(大缓存的安全性)
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
const redisHandler = await createRedisHandler({
client: createClient({
url: process.env.REDIS_URL,
}),
keyPrefix: "myApp:",
sharedTagsKey: "myTags",
sharedTagsTtlKey: "myTagTtls",
});
自定义值序列器
默认情况下,处理器将每个条目存储为 JSON 字符串(JSON.stringify 编号 : JSON.parse),匹配早先的发布。 你可以用自己的线条格式来收缩有效载荷(压缩),添加加密,或者使用另一个编码——这个软件包不会运送额外的可解码器,所以你的依赖性保持在最小.
合同
serialize(value)接收完整缓存对象 Next.js 输入(元数据如tags, (中文(简体) ),lastModified, (中文(简体) ),lifespan,加上被窝value有效载荷)。 它必须返回适合Redis的字符串SET。 。 。 .deserialize(stored)从 Redis 接收此字符串GET返回与处理器兼容的解析对象,或null将密钥当作错漏(删除已删除的条目).
处理器正常 Buffer 有效载荷内到字符串 serialize,然后在 deserialize,所以一个平地 JSON.stringify 编号 : JSON.parse 往返旅行仍然有效.
默认导出重用
如果您想要包装或比较行为, 您可以导入内置序列器 :
import createRedisHandler, {
jsonCacheValueSerializer,
} from "@fortedigital/nextjs-cache-handler/redis-strings";
示例: gzip
当缓存项是大文本(RSC有效载荷,HTML)时有用. 使用节点的内置 zlib· ; gzipSync 编号 : gunzipSync 缓存时运行在服务器上, 如果您的流量非常热, 则读取并写入配置 .
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
import { gzipSync, gunzipSync } from "zlib";
const redisCacheHandler = createRedisHandler({
client: redisClient,
keyPrefix: "nextjs:",
valueSerializer: {
serialize: (value) => gzipSync(JSON.stringify(value)).toString("base64"),
deserialize: (stored) => {
const parsed = JSON.parse(
gunzipSync(Buffer.from(stored, "base64")).toString("utf-8"),
);
return parsed;
},
},
});
示例: 溴化物
当缓存项是大文本(RSC有效载荷,HTML)时有用. 使用节点的内置 zlib· ; brotliCompress 编号 : brotliDecompress 缓存时运行在服务器上, 如果您的流量非常热, 则读取并写入配置 .
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
import { brotliCompress, brotliDecompress } from "zlib";
import { promisify } from "util";
const brotliCompressAsync = promisify(brotliCompress);
const brotliDecompressAsync = promisify(brotliDecompress);
const redisCacheHandler = createRedisHandler({
client: redisClient,
keyPrefix: "nextjs:",
valueSerializer: {
async serialize(value) {
const compressed = await brotliCompressAsync(
Buffer.from(JSON.stringify(value), "utf8"),
);
return compressed.toString("base64");
},
async deserialize(stored) {
const decompressed = await brotliDecompressAsync(
Buffer.from(stored, "base64"),
);
return JSON.parse(decompressed.toString("utf8"));
},
},
});
业务说明
- 变化
valueSerializer(或拖动压缩)使已存在的 Redis 密钥无法被读取,直到您冲出这些密钥或运行迁移;在部署时计划一个密钥前缀凸出或缓存. - Tag 映射和 TTL 侧车散列仍然被处理器作为 JSON 存储;只有主条目值使用您的序列器.
Redis 集群
import { createCluster } from "@redis/client";
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
import { withAdapter } from "@fortedigital/nextjs-cache-handler/cluster/adapter";
const { hostname: redisHostName } = new URL(process.env.REDIS_URL);
redis = withAdapter(
createCluster({
rootNodes: [{ url: process.env.REDIS_URL }],
// optional if you use TLS and need to resolve shards' ip to proper hostname
nodeAddressMap(address) {
const [_, port] = address.split(":");
return {
host: redisHostName,
port: Number(port),
};
},
}),
);
// after using withAdapter you can use redis cluster instance as parameter for createRedisHandler
const redisCacheHandler = createRedisHandler({
client: redis,
keyPrefix: CACHE_PREFIX,
});
使用电离层
如果你喜欢使用 ioredis 改为 @redis/client,可以使用 ioredisAdapter 帮手.
npm i ioredis
import Redis from "ioredis";
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
import { ioredisAdapter } from "@fortedigital/nextjs-cache-handler/helpers/ioredisAdapter";
const client = new Redis(process.env.REDIS_URL);
const redisClient = ioredisAdapter(client);
const redisHandler = createRedisHandler({
client: redisClient,
keyPrefix: "my-app:",
});
local-lru
本地lru Handler使用lru-cache QQ实例作为缓存商店. 它将缓存存储在内存中,并在缓存达到极限时驱逐最近使用最少的条目. 当共享缓存无法使用时, 您可以使用这个 Handler 作为倒置缓存 .
QQ 本地- lru Handler 不适合生产环境. 它只用于发展和测试目的.
特征 :
- 使用密钥过期
EXAT或者说EXPIREAT - 基于标记的再验证
- 自动 TTL 管理
- 默认
revalidateTagQuerySize数字 :10_000(大缓存的安全性)
import createLruHandler from "@fortedigital/nextjs-cache-handler/local-lru";
const localHandler = createLruHandler({
maxItemsNumber: 10000,
maxItemSizeBytes: 1024 * 1024 * 500,
});
composite
路由缓存操作跨越多个基础处理器.
特征 :
- 多个后端支持
- 自定义路由策略
- 第一版阅读策略
import createCompositeHandler from "@fortedigital/nextjs-cache-handler/composite";
const compositeHandler = createCompositeHandler({
handlers: [handler1, handler2],
setStrategy: (data) => (data?.tags.includes("handler1") ? 0 : 1),
});
仪器
初始缓存登记
默认, registerInitialCache 通过覆盖任何已存在的缓存来填充
包含由构建时的文物生成的值的条目(比对调,页面,路由).
初始缓存写入策略
如果您想要保存缓存中可能已经存在的值( 例如, ),
在另一个实例的运行时写入条目,您可以启用
setOnlyIfNotExists 选项 :
await registerInitialCache(CacheHandler, {
setOnlyIfNotExists: true,
});
当启用时, 在初始缓存注册期间执行的缓存写入将仅是 如果相应的缓存密钥不存在,则发生。 这让你可以 明确选择缓存数量策略,而不是执行单一的默认.
实例
示例项目
这个 实例项目 next.js 缓存功能的综合演示有交互式实例:
- 默认缓存 - 证明
force-cache行为 - 无存储 - 显示
no-store对于永远新鲜的数据 - 时间调整 - 自动缓存重新验证
- 用标签获取 - 基于标签的缓存无效
- 不稳定缓存( c) - 有标记的函数缓存
- 以色列 - 递增静态再生
- 静态参数 - 动态路线静态生成
举例来说:
pnpm install
cd examples/redis-minimal
npm run build
npm run start
说明: 缓存只在生产模式下起作用. 见 实例 更多细节.
生产设置示例
这是完整的生产准备 cache-handler.js 示例:
import { createClient } from "redis";
import { PHASE_PRODUCTION_BUILD } from "next/constants.js";
import { CacheHandler } from "@fortedigital/nextjs-cache-handler";
import createLruHandler from "@fortedigital/nextjs-cache-handler/local-lru";
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
import createCompositeHandler from "@fortedigital/nextjs-cache-handler/composite";
CacheHandler.onCreation(() => {
// Important - It's recommended to use global scope to ensure only one Redis connection is made
// This ensures only one instance get created
if (global.cacheHandlerConfig) {
return global.cacheHandlerConfig;
}
// Important - It's recommended to use global scope to ensure only one Redis connection is made
// This ensures new instances are not created in a race condition
if (global.cacheHandlerConfigPromise) {
return global.cacheHandlerConfigPromise;
}
// You may need to ignore Redis locally, remove this block otherwise
if (process.env.NODE_ENV === "development") {
const lruCache = createLruHandler();
return { handlers: [lruCache] };
}
// Main promise initializing the handler
global.cacheHandlerConfigPromise = (async () => {
let redisClient = null;
if (PHASE_PRODUCTION_BUILD !== process.env.NEXT_PHASE) {
const settings = {
url: process.env.REDIS_URL,
pingInterval: 10000,
};
// This is optional and needed only if you use access keys
if (process.env.REDIS_ACCESS_KEY) {
settings.password = process.env.REDIS_ACCESS_KEY;
}
try {
redisClient = createClient(settings);
redisClient.on("error", (e) => {
if (typeof process.env.NEXT_PRIVATE_DEBUG_CACHE !== "undefined") {
console.warn("Redis error", e);
}
global.cacheHandlerConfig = null;
global.cacheHandlerConfigPromise = null;
});
} catch (error) {
console.warn("Failed to create Redis client:", error);
}
}
if (redisClient) {
try {
console.info("Connecting Redis client...");
await redisClient.connect();
console.info("Redis client connected.");
} catch (error) {
console.warn("Failed to connect Redis client:", error);
await redisClient
.disconnect()
.catch(() =>
console.warn(
"Failed to quit the Redis client after failing to connect.",
),
);
}
}
const lruCache = createLruHandler();
if (!redisClient?.isReady) {
console.error("Failed to initialize caching layer.");
global.cacheHandlerConfigPromise = null;
global.cacheHandlerConfig = { handlers: [lruCache] };
return global.cacheHandlerConfig;
}
const redisCacheHandler = createRedisHandler({
client: redisClient,
keyPrefix: "nextjs:",
});
global.cacheHandlerConfigPromise = null;
// This example uses composite handler to switch from Redis to LRU cache if tags contains `memory-cache` tag.
// You can skip composite and use Redis or LRU only.
global.cacheHandlerConfig = {
handlers: [
createCompositeHandler({
handlers: [lruCache, redisCacheHandler],
setStrategy: (ctx) => (ctx?.tags.includes("memory-cache") ? 0 : 1), // You can adjust strategy for deciding which cache should the composite use
}),
],
};
return global.cacheHandlerConfig;
})();
return global.cacheHandlerConfigPromise;
});
export default CacheHandler;
参考原始软件包
这个项目最初的基础是: @neshca/cache-handler之前的版本 2.0.0 包裹或延长原作。 截止 2.0.0,该项目完全独立,不再使用或要求 @neshca/cache-handler。 。 。 .
对于上下文或历史文献,您仍可参考 原项目。 。 。 .
API 参考链接
Next.js 文档
- 下一个. js 缓存 - 下个.js缓存综合指南
- 数据获取、缓存和修改 - 获取 API 缓存选项
fetchAPI 密码 - 下一个.js 获取选项(next.revalidate, (中文(简体) ),next.tags(中文(简体) )revalidateTag- 基于标签的缓存无效revalidatePath- 基于路径的缓存无效updateTag- 立即缓存无效(下段js 16)unstable_cache- 函数缓存- 路由段配置 - 说吧
revalidate, (中文(简体) ),dynamic等 (中文(简体) ). - 递增静态再生 - 内部审计记录
Redis 文档
- Redis 节点客户端 - 官方Redis客户端库
- Redis 文档 - Redis 服务器文档
- Redis 命令 - Redis 命令参考
解决问题
缓存不在开发模式中工作
问题: 跑步时卡车好像不起作用 npm run dev。 。 。 .
解决方案 : 这是预期的行为。 Next.js 故意禁用开发模式中的缓存以更快地热重装. 要测试缓存功能, 您必须使用生产模式 :
npm run build
npm run start
重做连接出错
问题: 获取连接错误或“ Redis客户端尚未准备好” 错误 .
解决方案 :
- 校验 Redis 正在运行 :
redis-cli ping应返回PONG - 检查
REDIS_URL环境变量设置正确 - 确保Redis可以从您的应用程序中访问( 请检查防火墙/ 网络设置)
- 用于生产, 验证 Redis 证书和连接字符串格式
- 检查 Redis 日志的连接问题
重新验证 Tag 后缓存不无效
问题: 通话 revalidateTag() 似乎没有清除缓存.
解决方案 :
- 在接下来的.j 16,确保你使用
revalidateTag(tag, cacheLife)与所需cacheLife参数 - 精确验证标签匹配( 标牌是区分大小写)
- 检查缓存项是用同一标签创建的
- 在开发模式中,缓存被禁用 - 在生产模式中测试
从Next.js移徙 14
问题: 从Next.js 14到15/16的升级后出错.
解决方案 : Next.js 14和15之间的缓存格式不兼容 . 您必须冲出您的 Redis 缓存 在运行新版本之前 :
redis-cli FLUSHALL
如果使用特定数据库:
redis-cli -n <database-number> FLUSHDB
版本相容性问题
问题: 软件包版本与您的 Next.js 版本不兼容.
解决方案 :
- Next.js 15:使用 2.0.0+ (3.0.0+建议)版本
- Next.js 16: 使用版本 3.0.0+(需要)
- 检查 版本要求 节
- 校验您的节点. js 版本是 \ 22. 0.0
调试缓存行为
问题: 需要调试缓存发生的情况.
解决方案 : 通过设置环境变量启用调试记录 :
NEXT_PRIVATE_DEBUG_CACHE=1 npm run start
这将输出详细的缓存操作日志来帮助诊断问题.
捐款
这个项目使用 涡轮 以主软件包和实例管理单列结构.
先决条件
- 节点.js 22.0.0
- 时间:
发展 工作流量
- 启动 Dev 服务器数字 :
pnpm dev(平行运行所有 dev 服务器) - 运行所有测试数字 :
pnpm test - 运行 e2e 测试数字 :
pnpm test:e2e
运行e2e测试
Playwright 集成测试启用 examples/redis-minimal。他们在生产模式中运行实例应用程序,以对抗真正的 Redis 实例,并断言缓存行为(tag/path revalidation,包括Pages路由器).
先决条件: Redis 本地运行( 默认) redis://localhost:6379) (中文(简体) ).
遗产/贬值
nish Classic 缓存
⚠️ 折旧: 此功能从@neshca 迁移出来只是为了兼容的目的 。 谨慎使用 -- -- 没有计划进一步发展或提供支持.
移徙: 使用 unstable_cache 相反,它提供了类似的功能和更好的Next.js集成.
neshClassicCache 允许您缓存昂贵操作的结果,比如数据库查询,然后在多个请求中重复使用。 与 neshCache 或者说 unstable_cache ↗ 函数, neshClassicCache 必须在 Next.js Pages 路由器中使用,允许用户在 getServerSideProps 和API路线.
[注]
创建缓存条目
neshClassicCache只能通过revalidateTag↗ 方法.
参数
fetchData- 一个同步函数,可以获取要缓存的数据。 它必须是函数返回一个Promise。 。 。 .commonOptions- 控制缓存行为的一个对象:tags- 与缓存结果关联的标记系列revalidate- 重新验证间隔数秒argumentsSerializer- 将参数序列化的函数(默认为JSON.stringify(中文(简体) )resultSerializer- 将结果序列化的职能resultDeserializer- 消除结果的功能responseContext- 反应上下文对象
示例
import { neshClassicCache } from "@fortedigital/nextjs-cache-handler/functions";
import axios from "axios";
const cachedAxios = neshClassicCache(async (url) => {
return (await axios.get(url.href)).data;
});
export default async function handler(request, response) {
const data = await cachedAxios(
{ revalidate: 5, tags: ["api-data"], responseContext: response },
new URL("https://api.example.com/data.json"),
);
response.json(data);
}
许可证
凭 MIT 许可证,与原件一致 @neshca/cache-handler。 。 。 .