Static Crawler & Resolver (build-static-pages.js)
Crawl your codebase routes, inspect layout structures, mock client requests, and resolve static route configurations.
Key File Location: ./dinou/core/build-static-pages.js💡 Overview
In Dinou's architecture, static pages are evaluated and pre-rendered at server startup to provide near-instant loading speeds. The build-static-pages.js script acts as the central router crawler and static route evaluator. It traverses the filesystem to find page entry points, runs a mock render pass inside a simulated request context to detect if the page performs dynamic checks (which would cause a bailout), and registers the route and its metadata in-memory if no bailout occurs. This allows the caller orchestrator to trigger the actual RSC and HTML page compilation.
📊 Architecture Flow
The diagrams below demonstrate how routes are crawled and registered in bulk at server startup, versus how single paths are resolved at runtime:
1. Bulk Startup Pass (buildStaticPages)
2. Runtime Single-Route Pass (buildStaticPage)
🛡️ 1. Dynamic Bailout Proxy
A page is considered static only if its output is identical for all users. If a component reads request-specific parameters (such as browser cookies or custom HTTP headers), the page must run dynamically on every request.
Dinou accomplishes this using Javascript Proxy wrapper spies. When mock-rendering a page, the evaluator injects proxies in place of cookies() and headers(). If any property is accessed during the render cycle, a bailout callback runs and marks the route as dynamic:
function createBailoutProxy(target, label, onBailout) {
const safeTarget = target || {};
return new Proxy(safeTarget, {
get(t, prop, receiver) {
// Ignore internal Node.js and Console debugging symbols
if (
typeof prop === "symbol" ||
prop === "inspect" ||
prop === "valueOf" ||
prop === "toString"
) {
return Reflect.get(t, prop, receiver);
}
// 🚨 ALARM: Dynamic access detected during static generation
console.log(`[StaticBailout] Access to ${label} detected: "${String(prop)}".`);
// Execute callback to mark page compile state as dynamic
onBailout();
// Return the value from the original mock object
return Reflect.get(t, prop, receiver);
},
ownKeys(t) {
console.log(`[StaticBailout] Iteration of ${label} detected.`);
onBailout();
return Reflect.ownKeys(t);
},
has(t, prop) {
console.log(`[StaticBailout] Existence check (IN) in ${label}: "${String(prop)}".`);
onBailout();
return Reflect.has(t, prop);
}
});
}🔍 2. Directory Crawler & Route Collector
During production server startup, the collectPages() recursive method traverses your src/ directory:
- Static Folders & Route Groups: Skips parentheses directories (e.g.
(auth)) when building the URL path but crawls their contents. - Dynamic Routes: If it encounters a dynamic folder (e.g.
[id]or catch-all[[...slug]]), it imports the adjacentpage_functions.jsfile, runs thegetStaticPaths()API, and resolves all valid parameter combinations. - Gap Check: Evaluates catch-all parameter arrays to ensure there are no empty segments between active parameters, preventing malformed URLs.
🎭 3. Mock Request & Response Context
Since Server Components execute in a request container, Dinou mocks standard Express-like HTTP request and response structures before triggering the render.
The mock objects fulfill the server contracts, allowing the page to execute safely and warn about potential compilation conflicts (such as triggering a HTTP redirect during a static build pass):
// Mock Response matches the standard server middleware contract
const mockRes = {
_statusCode: 200,
_headers: {},
_redirectUrl: null,
_cookies: [],
cookie(name, value, options) {
this._cookies.push({ name, value, options, isClear: false });
},
clearCookie(name, options) {
this._cookies.push({ name, value: "", options, isClear: true });
},
setHeader(name, value) {
this._headers[name.toLowerCase()] = value;
},
status(code) {
this._statusCode = code;
},
redirect(arg1, arg2) {
let status = 302;
let url = "";
if (typeof arg1 === "number") {
status = arg1;
url = arg2;
} else {
url = arg1;
}
this._statusCode = status;
this._redirectUrl = url;
console.warn(`⚠️ [SSG] Redirect detected in static compile -> ${url} (${status})`);
}
};
// Spies throw flags when read during compilation
const cookiesProxy = createBailoutProxy({}, "Cookies", markAsDynamic);
const headersProxy = createBailoutProxy({}, "Headers", markAsDynamic);
const queryProxy = createBailoutProxy({}, "Query", markAsDynamic);
const mockReq = {
query: queryProxy,
cookies: cookiesProxy,
headers: headersProxy,
path: reqPath,
method: "GET",
};
const mockContext = { req: mockReq, res: mockRes };🏗️ 4. Static Route Resolver
The evaluation of each individual route is encapsulated in buildStaticPage(). It resolves layout hierarchies, feeds props derived from getProps(), renders the component tree, and records the output if no bailout occurred:
async function buildStaticPage(reqPath, isDynamic = null) {
const srcFolder = path.resolve(process.cwd(), "src");
try {
const segments = reqPath.split("/").filter(Boolean);
let folderPath = srcFolder;
let dynamicParams = {};
// 1. Resolve physical path segments and extract dynamic parameters...
// (Crawl directories, evaluate catch-alls, check config parameters)
let isStatic = true;
const markAsDynamic = () => {
isStatic = false;
if (isDynamic) isDynamic.value = true;
};
// 2. Set up context spies
const mockContext = createMockContext(reqPath, markAsDynamic);
// 3. Render inside Server AsyncLocalStorage Context
await requestStorage.run(mockContext, async () => {
const [pagePath, dParams] = getFilePathAndDynamicParams(
segments,
{},
folderPath,
"page",
true,
true,
undefined,
segments.length,
dynamicParams
);
if (!pagePath) throw new Error(`No page found for ${reqPath}`);
const pageModule = await importModule(pagePath);
const Page = pageModule.default ?? pageModule;
let props = { params: dParams };
// Load static props if getProps() exists in page_functions.js
const [pageFunctionsPath] = getFilePathAndDynamicParams(
segments,
{},
folderPath,
"page_functions",
true,
true
);
if (pageFunctionsPath) {
const pageFuncs = await importModule(pageFunctionsPath);
if (isDynamic && (isDynamic.value = pageFuncs.dynamic?.())) {
return; // Early bailout if page is hard-configured as dynamic
}
const getProps = pageFuncs.getProps;
const pageProps = await getProps?.(dParams);
props = { ...props, ...(pageProps?.page ?? {}) };
}
// Render React Server Component (RSC) element tree
const jsx = React.createElement(Page, props);
if (!isStatic) {
if (isDynamic) isDynamic.value = true;
return;
}
staticRoutes.add(reqPath);
staticMetadata.set(reqPath, {
revalidate: revalidate?.(),
effects: { redirect: mockRes._redirectUrl, cookies: mockRes._cookies },
tags: cacheTags,
});
});
} catch (err) {
console.error(`[SSG] Error evaluating ${reqPath}:`, err);
}
}🎯 Integration & Calling Modules
Dinou isolates route evaluation from file generation. The modules in build-static-pages.js are imported and invoked by different orchestration engines depending on the lifecycle phase:
1. buildStaticPages() — Bulk Startup Pass
Called once by the main static builder entry point (generate-static.js) during production server startup. It performs the initial directory crawl to discover all static routes (including parameters fetched from getStaticPaths()) and populates the in-memory route registry.
2. buildStaticPage() — Runtime Single-Route Pass
Invoked dynamically to evaluate a single route and check for dynamic proxy bailouts. It is called by three runtime engines:
revalidating.js(Background ISR): Checks if a stale cache page has become dynamic before writing its background revalidation.generating-isg.js(On-Demand ISG): Mock-renders dynamic parameter routes on their first request to verify if they can be cached statically.cache-revalidate.js(On-Demand Revalidation API): Evaluates the target path when forced to purge cache by a manual revalidation request.