在独立模式下,Next.js 会在构建过程中预构建 ISR 缓存。并且在运行时,NextServer 期望此缓存位于服务器的本地。当服务器运行在单台 Web 服务器机器上,在所有请求之间共享缓存时,这会有效运行。在 Lambda 环境中,缓存需要集中存放在所有服务器 Lambda 函数实例均可访问的位置。S3 充当了这个中心位置。
为了实现这一点:
- ISR 缓存文件被排除在
server-functionbundle 之外,而是上传到缓存桶。 - 通过在
next.config.js中配置incrementalCacheHandlerPath(opens in a new tab) 字段,默认缓存处理程序被自定义缓存处理程序替换。 - 自定义缓存处理程序管理 S3 上的缓存文件,处理读取和写入操作。
- 由于我们使用的是 FIFO 队列,如果我们想一次处理多个重新验证,我们需要单独的 Message Group ID。我们基于路由路径为每个重新验证请求生成一个 Message Group ID。这确保了对同一路由的重新验证请求只被处理一次。你可以使用
MAX_REVALIDATE_CONCURRENCY环境变量来控制同时处理的重新验证请求数量。默认情况下,它设置为 10。 revalidation-function从队列中轮询消息,并向带有x-prerender-revalidate头的路由发出HEAD请求。server-function接收HEAD请求并重新验证缓存。- 标签在 DynamoDB 表中以不同方式处理。我们使用单独的表来存储每个路由的标签。自定义缓存处理程序在更新缓存时会更新表中的标签。
过期页面 ISR 请求的生命周期
- Cloudfront 接收页面请求。假设页面在 Cloudfront 中已过期。
- Cloudfront 在后台将请求转发给
server-function,但仍返回缓存版本。 server-function检查 S3 缓存。如果页面已过期,它将过期响应发送回 Cloudfront,同时向重新验证队列发送消息以触发后台重新验证。它还会将 cache-control 头更改为s-maxage=2, stale-while-revalidate=2592000- 2 秒后同一个页面有新的请求进入。Cloudfront 将缓存版本发送回用户,并将请求转发给
server-function。 - 如果重新验证完成,
server-function将更新缓存并将更新后的响应发送回 Cloudfront。后续请求将获得更新后的版本。否则,我们回到步骤 3。
标签
标签存储在 DynamoDB 表中。
表中有 3 个字段:tag、path、revalidatedAt。tag 字段是分区键,path 是排序键。
我们使用一个名为 revalidate 的索引,其中 path 作为分区键,revalidatedAt 作为排序键。
每个标签有多个路径,每个子路径也被视为一个标签。例如,如果我们有一个标签 tag1 路径为 /a/b/c,我们也有标签 /a、/a/layout、/a/page、/a/b、/a/b/layout、/a/b/page、/a/b/c/layout、/a/b/c/page。
当调用 revalidateTag 时,我们更新与此标签关联的每个路径和子路径的 revalidatedAt 值。
当我们检查页面是否过期时,我们检查每条记录的 revalidatedAt 值和此 S3 缓存对象的 LastModified。如果 revalidatedAt 大于 LastModified,我们认为页面已过期。
成本
请注意,fetch 缓存正在使用 S3。next 中默认情况下 fetch 是被缓存的,即使是 SSR 请求,它
也会被写入 S3。这可能导致大量的 S3 请求并且可能很昂贵。你可以通过在 fetch 选项中将 cache 设置为 no-store 来禁用 fetch
缓存。另见 此
解决方法
get 将在每次请求未在 Cloudfront 中缓存的 ISR 和 SSG 时被调用,set 将在每次重新验证时被调用。
它们也可能在 fetch 请求中被调用,如果 cache 选项未设置为 no-store。
部署也有一些相关成本,因为你需要将缓存上传到 S3 并将标签上传到 DynamoDB。
对于这里的示例,让我们假设一个应用路由在 us-east-1 中有 5 分钟的重新验证延迟。这是假设你获得通往该路由的恒定流量(如果没有流量,你只需支付存储成本)。
S3
- 每次对缓存的
get请求将导致至少 1 个GetObject
GetObject 成本 - 8,640 请求 * $0.0004 每 1,000 请求 = $0.003456
总成本 - 每条路由每月 $0.003456- 每次对缓存的
set请求将导致 1 个 S3 中的PutObject
PutObject 成本 - 8,640 请求 * $0.005 每 1,000 请求 = $0.0432
总成本 - 每条路由每月 $0.0432然后你可以根据你的使用量和 S3 定价 (opens in a new tab) 计算成本
DynamoDB
对于示例,让我们考虑同一路由有 2 个标签,每个标签有 10 个路径和子路径。这是假设你获得通往该路由的恒定流量。
- 每次
revalidateTag请求将导致 1 个 DynamoDB 中的Query和每个与标签关联的路径的PutItem,它们在BatchWriteItem请求中以 25 个为一组进行分组。
假设你每 5 分钟进行 1 次重新验证
Query 成本 - 8,640 请求 * $0.25 每 1,000,000 读 = $0.00216
BatchWriteItem 成本 - 86,400 请求 * $0.25 每 1,000,000 写 = $0.0216
总成本 - 每个标签重新验证每月 $0.04536- 每次
get请求将导致 1 个 DynamoDB 中的Query
Query 成本 - 8,640 请求 * $0.25 每 1,000,000 读 = $0.00216
总成本 - 每条路由每月 $0.00216- 每次
set请求将导致 1 个 DynamoDB 中的Query和每个与路径关联且不存在于 DynamoDB 中的标签的PutItem,它们在BatchWriteItem请求中以 25 个为一组进行分组。
Query 成本 - 8,640 请求 * $0.25 每 1,000,000 读 = $0.00216
总成本 - 每条路由每月 $0.00216然后你可以根据你的使用量和 DynamoDB 定价 (opens in a new tab) 计算成本