之前写了两个 demo讲解了如何实现 SSR和 SSG,今天再写个 demo说在 ISR如何实现。
(资料图片)
ISR即 Incremental Static Regeneration增量静态再生,是指在 SSG的前提下,可以在收到请求时判定页面是否需要刷新,如果需要则重新构建该页面,这样既拥有了静态页面的优势又可以避免页面长时间未更新导致信息过时。且由于在页面维度验证,所以每次可以只构建特定的页面。
ISR一般适用于符合 SSG场景,但是却对页面的时限性有一定要求时。
简单的 ISR实现也很简单,只需要在收到页面请求时按照更新策略判断是否需要需要重新生成页面,如果需要触发页面的构建更新。需要注意一般情况下生成页面不会影响页面的响应,而是后台去做构建。
现在就基于之前写的 SSG demo,做一下改造让其支持 ISR。
由于 ISR构建会同时在构建脚本和服务器中触发,所以需要对之前的代码做一些小小的改动。
首先抽离出一个通用的构建函数(由于服务器会使用到尽量避免同步代码):
import fs from "fs/promises";import { renderToString } from "react-dom/server";import React from "react";import Post from "./ui/Post";import List from "./ui/List";async function build(type: "list"): Promise;async function build(type: "post", name: string): Promise;async function build(type: "list" | "post", name?: string) { if (type === "list") { const posts = await fs.readdir("posts"); await fs.writeFile( "dist/index.html", `${renderToString( { delete require.cache["posts/" + post]; return { ...require("./posts/" + post), key: post.replace(".json", "") }; })} /> )}
` ); } else { delete require.cache["posts/" + name]; const postInfo = require("./posts/" + name); const fileName = `dist/posts/${name}.html`; await fs.writeFile(fileName, `${renderToString( )}`); }}export default build; 这样就可以通过 build函数来构建指定的 post或者 list页面。
然后再将原先的构建脚本做一下简单的修改:
import fs from "fs";import build from "./build-util";// make sure the dir existsif (!fs.existsSync("dist")) { fs.mkdirSync("dist");}if (!fs.existsSync("dist/posts")) { fs.mkdirSync("dist/posts");}// get all the files in postsconst posts = fs.readdirSync("posts");(async () => { for await (const post of posts) { await build("post", post.replace(".json", "")); } await build("list");})();服务器由于 ISR需要在请求时做是否构建的判定,所以原先的静态服务器方案无法继续使用,我们换成 express来实现:
import express from "express";import path from "path";import fs from "fs";import build from "../build-util";const app = express();const expiresTime = 1000 * 60 * 10;app.use(function (req, res, next) { setTimeout(() => { const filename = req.path.indexOf(".html") >= 0 ? req.path : req.path + "index.html"; // get the file"s create timestamps fs.stat(path.join("./dist", filename), function (err, stats) { if (err) { console.error(err); return; } if (Date.now() - +stats.mtime > expiresTime) { console.log(filename, "files expired, rebuilding..."); if (filename === "/index.html") { build("list"); } else { build("post", path.basename(filename).replace(".html", "")); } } }); }); next();});app.use(express.static("dist"));app.listen(4000, () => { console.log("Listening on port 4000");});我们增加一个 express的中间件,让其来判定文件是否过期,这里以十分钟为例,实际场景可按需定义过期判定。这里过期后就会调用 build文件来重新构建该文件。要注意此处先返回再构建,所以用户不会等待构建,并且此次访问依旧是旧的内容,构建完成后访问的才是新的内容。
post源文件的修改时间等等总结ISR对比 SSG可以有效的控制页面的时效性,但也要付出额外的代价:
没有最佳,只有最适合,所以实际场景下还是按需选用。
最后本文的 demo代码放置在 React ISR Demo 中,可自行取阅。