Background ISR Engine (revalidating.js)
Explore how Dinou handles background cache updates (ISR) when pages expire, using locks to prevent duplicate rendering tasks.
Key File Location: ./dinou/core/revalidating.js💡 Overview
Incremental Static Regeneration (ISR) enables you to keep your site static without rebuilding the entire application. When a user requests an expired route, the server immediately serves the stale page from disk (eliminating TTFB latency) and schedules an asynchronous compilation task in the background to refresh the cache.
📊 Revalidation Lifecycle
The flowchart below shows how checks are run in parallel to the user response loop to trigger background builds:
🔒 Mutex Lock & Concurrency Control
If an expired page experiences high concurrent traffic (e.g. thousands of request hits in a single second), running a background render task for each request would crash the CPU.
Dinou prevents this via a shared Mutex Lock pool:
- Lock Registration: The engine registers a
Setstructure calledregenerating. - Check and Block: Before starting compilation, the engine evaluates
regenerating.has(reqPath). If it returns true, the task exits immediately, bypassing parallel execution. - Cleanup: Inside a
finallyblock, the lock is released usingregenerating.delete(reqPath), enabling the next cache pass when expiration occurs.
🎯 Invocation & SWR Serving (Where is it called?)
The revalidating function is imported and called by the main web server (core/server.js) inside the request routing middleware when intercepting GET requests.
To implement the Stale-While-Revalidate (SWR) pattern, the server checks the route conditions, triggers the background compilation, and immediately serves the cached file (meaning the client doesn't wait for compilation):
// Inside core/server.js:
if (!isDevelopment && !dynamicState.value && pagePath && !isPathBlocked) {
revalidating(reqPath, dynamicState); // Calls the background SWR engine
let htmlPathOld;
if (regenerating.has(reqPath)) {
// If compilation is currently active, fall back to the backup stale file
htmlPathOld = path.join("dist2", reqPath, "index._old.html");
}
const htmlPath = path.join("dist2", reqPath, "index.html");
const fileToRead = htmlPathOld || htmlPath;
// Instantly serve the cached file to the user
if (existsSync(fileToRead) && !dynamicState.value) {
res.setHeader("Content-Type", "text/html");
res.statusCode = getStatus(reqPath) || 200;
return fs.createReadStream(fileToRead).pipe(res);
}
}⚙️ Complete Code Walkthrough
Below is the full, complete code of revalidating.js responsible for checking timestamps and rebuilding routes:
const path = require("path");
const fs = require("fs").promises;
const { existsSync, copyFileSync } = require("fs");
const generateStaticPage = require("./generate-static-page");
const { buildStaticPage } = require("./build-static-pages");
const generateStaticRSC = require("./generate-static-rsc");
const { safeRename } = require("./safe-rename");
const { updateStatus } = require("./status-manifest");
const regenerating = new Set(); // Mutex pool to prevent duplicate builders
function revalidating(reqPath, isDynamicFromServer) {
const dist2Folder = path.resolve(process.cwd(), "dist2");
const metadataPath = path.join(dist2Folder, reqPath, "metadata.json");
// 1. Concurrency Check
if (regenerating.has(reqPath)) return;
fs.readFile(metadataPath, "utf8")
.then((content) => {
const metadata = JSON.parse(content);
const { revalidate, generatedAt } = metadata;
// 2. Expiration Check (Compare timestamps against metadata.json)
const isExpired =
typeof revalidate === "number" &&
revalidate > 0 &&
Date.now() > generatedAt + revalidate;
if (isExpired && !regenerating.has(reqPath)) {
try {
// 3. Back up the current page to avoid blank reads during compile
if (existsSync(path.join(dist2Folder, reqPath, "index.html")))
copyFileSync(
path.join(dist2Folder, reqPath, "index.html"),
path.join(dist2Folder, reqPath, "index._old.html")
);
if (existsSync(path.join(dist2Folder, reqPath, "rsc.rsc")))
copyFileSync(
path.join(dist2Folder, reqPath, "rsc.rsc"),
path.join(dist2Folder, reqPath, "rsc._old.rsc")
);
} catch (e) {
console.error("[ISR] copyFileSync error:", e);
}
// 4. Lock path segment to block concurrent compilation loops
regenerating.add(reqPath);
(async () => {
try {
console.log(`[ISR] Starting regeneration for ${reqPath}...`);
const isDynamic = {};
// Re-render Page & Layout components
await buildStaticPage(reqPath, isDynamic);
// Dynamic Bailout evaluation
if (isDynamic.value) {
isDynamicFromServer.value = true;
console.log(`[ISR] Bailout detected for ${reqPath}. Switching to Dynamic.`);
return;
}
// Generate RSC payload to temp folder
const rscResult = await generateStaticRSC(reqPath);
if (!rscResult.success) {
console.warn(`⚠️ [ISR] RSC generation failed for ${reqPath}. Aborting.`);
await fs.unlink(rscResult.tempPath).catch(() => {});
return;
}
// Commit RSC payload first (required for HTML compiler)
await safeRename(rscResult.tempPath, rscResult.finalPath);
// Generate static HTML
const pageResult = await generateStaticPage(reqPath);
if (pageResult.success) {
await safeRename(pageResult.tempPath, pageResult.finalPath);
updateStatus(reqPath, pageResult.status);
isDynamicFromServer.value = false;
console.log(`✅ [ISR] Successfully committed ${reqPath} (Status: ${pageResult.status})`);
} else {
console.warn(`⚠️ [ISR] HTML generation failed for ${reqPath}. Aborting commit.`);
await fs.unlink(pageResult.tempPath).catch(() => {});
}
} catch (e) {
console.error(`[ISR] Critical error regenerating ${reqPath}:`, e);
} finally {
// 5. Release Lock regardless of outcome
regenerating.delete(reqPath);
}
})();
}
})
.catch((err) => {});
}💾 Backup & Double-buffer Commit
To avoid files being corrupt or partially written during compilation:
- Copy to Stale: The engine copies current files to
index._old.htmlandrsc._old.rsc. If a request hits during compilation, the Express server falls back to serve these files. - Double-buffered compilation: Builders render HTML and RSC payloads to temporary files (e.g.,
index.html.tmp). - Atomic Commit: Once compilation completes successfully,
safeRename()executes a native file rename operation (which is atomic at OS-level), replacing the old file without page downtime.