阅读视图

发现新文章,点击刷新页面。

Nextjs ISR 企业落地实战

背景

Nextjs 项目本来使用的是 SSG 来渲染门户网站的 blog 的,目录如下:

image.png

这样技术实现是简单,但是维护成本比较高。运营和销售同学写完营销文章后,需要推送给研发,由研发录入到 git 仓库中,并且执行一遍发布流程。这样做,没有办法将文章撰写作为一个独立的营销任务,必须借助技术发布。而且还有个问题,blog 越来越多,放在仓库里,会导致仓库大小越来越大:

image.png

需求与技术方案设计

于是我们就设计了一个这样的内部需求:

维护一个内部的 blog 发布平台,运营录入后点击发布,同时通知 Nextjs 项目触发更新文章。

技术方案:

  • 内部 blog 发布平台使用 antd + go 搭建,负责录入文章到数据库,并提供公网接口获取
  • Nextjs 改造为通过接口获取动态数据,设置缓存来优化访问;并暴露 API,内部 blog 发布平台触发更新后清除缓存,用户下次访问就回去拉取最新的数据源。

初步实现

内部 blog 发布平台

没啥说的,普通的后台管理系统,如图

image.png

md 编辑器就使用掘金的 bytemd

image.png

用户录入后,存入到数据库中,并暴露接口来获取。

Nextjs 项目改造

页面配置:

export const dynamic = 'auto'; // 允许页面缓存
export const dynamicParams = true;
export const revalidate = 259200; // 页面缓存 3 天(与 fetch 缓存时间一致)

将读取静态目录换为通过接口(自己开发 API 提供数据源)获取:

// SSR 页面
const postData = {
  Action: "GetPublishedArticleList",
  Lang: lng,
};

const startTime = Date.now();
const res = await fetch(blogApiUrl, {
  method: "POST",
  headers: {
    'remote_user': 'admin',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(postData),
  // 设置缓存:有效期3天(259200秒),使用标签以便外部API可以清除缓存
  next: {
    revalidate: 259200, // 3天
    tags: [`blog-list-${lng}`],
  },
});

// 拿到数据后处理
if (res.ok) {
    const response = await res.json();
    const count = response?.Data?.Total || 0;
    ...
}

...

然后暴露 API,负责清除 fetch 缓存:

image.png

该 API 要记得配置 cors 跨域和来源 ip 和频次限制。

此外,需要配置 api 请求拦截,避免被中间件等影响造成请求不到地址:

async headers() {
    return [
      {
        // 排除 /api 路径,让 API 路由自己处理 CORS
        source: "/((?!api).)*",
        headers: [
          {
            key: "Access-Control-Allow-Origin",
            value: "*", // Set your origin
          },
          {
            key: "Access-Control-Allow-Methods",
            value: "GET, POST, PUT, DELETE, OPTIONS",
          },
          {
            key: "Access-Control-Allow-Headers",
            value: "Content-Type, Authorization",
          },
        ],
      },
    ];
  },

ISR 工作流程

首次访问:

  • 服务端渲染(SSR), fetch 缓存
  • 生成 HTML 并缓存
  • 返回给用户

后续访问(3 天内):

  • 直接返回缓存的 HTML, 不重新渲染, 响应快

3 天后:

  • 第一个请求触发后台重新渲染
  • 更新全部缓存
  • 后续请求使用新缓存

调用 /api/revalidate-blog:

  • 立即清除缓存
  • 下次访问重新渲染

落地演示

新建一篇文章,点击发布,更新状态:

image.png

调用 revalidate API 触发缓存更新:

image.png

线上刷新查看:

image.png

成功!!

❌