⚠️

next/cache 重新验证需要 next@13.5.1 或更高版本才能正常工作。如果您使用的是较旧版本, 请升级。

按需重新验证

当您手动重新验证特定页面的 Next.js 缓存时,存储在 S3 上的 ISR 缓存文件将会更新。但是,仍然需要使 CloudFront 缓存失效:

// pages/api/revalidate.js
export default async function handler(req, res) {
  await res.revalidate("/foo");
  await invalidateCloudFrontPaths(["/foo"]);
  // ...
}

如果使用的是 pages 路由器,您还必须使 _next/data/BUILD_ID/foo.json 路径失效。BUILD_ID 的值可以在 .next/BUILD_ID 构建输出中找到,并且可以通过 process.env.NEXT_BUILD_ID 环境变量在运行时访问。

await invalidateCloudFrontPaths(["/foo", `/_next/data/${process.env.NEXT_BUILD_ID}/foo.json`]);

下面是 invalidateCloudFrontPaths() 函数的一个示例:

import { CloudFrontClient, CreateInvalidationCommand } from "@aws-sdk/client-cloudfront";
 
const cloudFront = new CloudFrontClient({});
 
async function invalidateCloudFrontPaths(paths: string[]) {
  await cloudFront.send(
    new CreateInvalidationCommand({
      // 在此处设置 CloudFront 分配 ID
      DistributionId: distributionId,
      InvalidationBatch: {
        CallerReference: `${Date.now()}`,
        Paths: {
          Quantity: paths.length,
          Items: paths,
        },
      },
    })
  );
}

请注意,手动 CloudFront 路径失效会产生费用。根据 AWS CloudFront 定价页面 (opens in a new tab)

每月前 1,000 个请求失效的路径不收取额外费用。此后,每个请求失效的路径收费 0.005 美元。

由于这些成本,如果多个路径需要失效,使通配符路径 /* 失效更经济。例如:

// 在前 1000 个路径之后,这将花费 $0.005 x 3 = $0.015
await invalidateCloudFrontPaths(["/page/a", "/page/b", "/page/c"]);
 
// 这将花费 $0.005,但也会使其他路由(如 "page/d")失效
await invalidateCloudFrontPaths(["/page/*"]);

对于通过 next/cache 模块 (opens in a new tab) 进行的按需重新验证,如果您想检索给定标签的关联路径,可以使用此函数:

function getByTag(tag: string) {
  try {
    const { Items } = await this.dynamoClient.send(
      new QueryCommand({
        TableName: process.env.CACHE_DYNAMO_TABLE,
        KeyConditionExpression: "#tag = :tag",
        ExpressionAttributeNames: {
          "#tag": "tag",
        },
        ExpressionAttributeValues: {
          ":tag": { S: `${process.env.NEXT_BUILD_ID}/${tag}` },
        },
      })
    );
    return (
      // 我们需要从路径中移除 buildId
      Items?.map(({ path: { S: key } }) => key?.replace(`${process.env.NEXT_BUILD_ID}/`, "") ?? "") ?? []
    );
  } catch (e) {
    error("Failed to get by tag", e);
    return [];
  }
}

修补 ISR 的 fetch 行为。仅适用于 next@13.5.1+

如果您在应用中使用 ISR 和 fetch,可能会遇到一个导致重新验证值不一致的 bug。问题在于它使用页面中所有 fetch 调用中最低的重新验证值进行重新验证,而不考虑它们各自的值。要修复此 bug,您需要使用以下代码片段修改根布局组件中的 fetch 函数

export default function RootLayout() {
  const asyncStorage = require("next/dist/client/components/static-generation-async-storage.external");
  //@ts-ignore
  const staticStore = (fetch as any).__nextGetStaticStore?.() || asyncStorage.staticGenerationAsyncStorage;
  const store = staticStore.getStore();
  store.isOnDemandRevalidate = store.isOnDemandRevalidate && !(process.env.OPEN_NEXT_ISR === 'true');
  return <>...</>;
}