⚠️

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,可能会遇到一个导致 revalidate 值不一致的 bug。该问题的表现是:页面会使用所有 fetch 调用中最小的 revalidate 值进行再生,而忽略它们各自的设置值。要修复此问题,您需要在根布局组件中通过以下代码片段修改 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 <>...</>;
}