Generated vs Non-Generated Routes in SXO Production: A Technical Comparison
In SXO, generated routes serve pre-rendered static HTML files with long-term caching headers and no runtime rendering, while non-generated routes execute full server-side rendering on every request with dynamic asset injection.
SXO (Simple eXtensible Open framework) implements a hybrid rendering architecture that allows individual routes to be either pre-rendered at build time or rendered on-the-fly per request. Understanding the difference between generated and non-generated routes in production is critical for optimizing cache behavior and server performance. This analysis examines the specific implementation details found in the gc-victor/sxo repository, focusing on the production request handling logic and route manifest structure.
How Routes Are Defined at Build Time
The distinction between route types is encoded in dist/server/routes.json during the build process. The loadRoutesManifest function in src/js/server/shared/routes-loader.js validates the generated boolean flag that controls production behavior.
Generated routes are produced by running sxo generate. This CLI command (located in src/js/cli/commands/generate.js) writes a static HTML file to the client output directory and sets generated: true in the manifest entry.
Non-generated routes bypass pre-rendering. The build process retains only the JSX module in the server bundle, and the manifest entry includes an assets object containing CSS and JavaScript file references instead of the generated flag.
The manifest structure reveals the difference immediately:
// Generated route entry
{
"filename": "index.html",
"jsx": "src/pages/index.jsx",
"generated": true
}
// Non-generated route entry
{
"filename": "about/index.html",
"jsx": "src/pages/about/index.jsx",
"assets": {
"css": ["about/index.A1.css"],
"js": ["about/index.A1.js"]
}
}
Production Request Handling Logic
When a request hits the server, the core handler in src/js/server/prod/core-handler.js checks the route.generated property to determine the execution path.
Generated Route Marker Response
For generated routes, the handler returns a specialized marker response instead of rendering HTML. This response includes the X-SXO-Generated: true header and a Cache-Control directive set to public, max-age=300 (5 minutes by default).
// src/js/server/prod/core-handler.js
if (route.generated === true) {
const headers = withSecurityHeaders(
{
"X-SXO-Generated": "true",
"X-SXO-Filename": route.filename,
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": CACHE_GENERATED,
},
securityHeaders,
);
const response = new Response(null, { status: HTTP_STATUS_OK, headers });
response._sxoGenerated = { route, params };
return response;
}
Production adapters (Node, Bun, Deno, or Cloudflare) intercept this marker. The createGeneratedRouteHandler helper function reads the pre-rendered file from disk and streams it to the client:
// src/js/server/prod/core-handler.js
export function createGeneratedRouteHandler({ staticDir, fileReader, joinPath }) {
return async function serveGeneratedRoute(response, request) {
const generatedInfo = getGeneratedRouteInfo(response);
if (!generatedInfo) return null;
const htmlPath = joinPath(staticDir, generatedInfo.route.filename);
const html = await fileReader.readText(htmlPath);
if (html === null) return null;
const headers = {
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": CACHE_GENERATED,
};
return request.method === "HEAD"
? new Response(null, { status: 200, headers })
: new Response(html, { status: 200, headers });
};
}
Non-Generated SSR Flow
Non-generated routes execute the full server-side rendering pipeline. The handler loads the JSX module, executes the export function, and injects assets dynamically.
// src/js/server/prod/core-handler.js
let page = await renderFn(params);
const isHtml = /^<html[\s>]/i.test(page);
if (route.assets && typeof route.assets === "object" && isHtml) {
page = injectAssets(page, route.assets, normalizedPublicPath);
}
This flow returns HTML with Cache-Control: public, max-age=0, must-revalidate, ensuring the response is never cached by intermediaries but remains public to allow client-side asset caching.
Caching and Performance Characteristics
The two route types exhibit fundamentally different caching semantics and computational costs.
Generated routes benefit from aggressive caching because the HTML never changes until the next sxo generate run. The production adapter serves the file directly from disk without executing JavaScript or injecting assets, resulting in minimal CPU overhead per request.
Non-generated routes incur the full cost of SSR on every request. The server must execute the route's JavaScript module, render the JSX to HTML, and inject the appropriate <link> and <script> tags. While this enables dynamic data fetching and per-request personalization, it requires max-age=0 to prevent stale content delivery.
According to src/js/server/prod.test.js, the test suite verifies that generated routes serve the exact HTML written during the generate phase without asset injection, while non-generated routes return HTML prefixed by <!doctype html> with injected asset tags.
Summary
- Generated routes are pre-rendered HTML files marked with
generated: truein the manifest, served directly from disk with 5-minute cache headers and no runtime execution. - Non-generated routes execute JSX modules on every request, receive dynamic asset injection via
injectAssets, and carrymax-age=0cache headers to prevent HTML caching. - The
createGeneratedRouteHandlerfunction in production adapters intercepts marker responses to serve static files, while non-generated flows use the core SSR handler insrc/js/server/prod/core-handler.js. - Choose generated routes for static content like marketing pages, and non-generated routes for dynamic content requiring server-side data or authentication.
Frequently Asked Questions
How do I convert a route from non-generated to generated in SXO?
Run the sxo generate command from the CLI. This executes src/js/cli/commands/generate.js, which pre-renders the specified routes to static HTML files and updates dist/server/routes.json to include the generated: true flag for those entries.
Why do generated routes still use the SSR handler at all?
The core handler always runs first to apply security headers, validate the route, and return the marker response with X-SXO-Generated. However, the actual HTML generation is skipped; production adapters detect the marker via response._sxoGenerated and delegate to createGeneratedRouteHandler to stream the static file instead of executing JSX.
Do generated routes support dynamic data fetching?
No. Generated routes serve static HTML files created at build time. If you need per-request data, authentication checks, or personalized content, you must use non-generated routes that execute the full SSR pipeline on every request.
What happens to CSS and JavaScript in generated routes?
Asset injection is bypassed for generated routes. The HTML file written during sxo generate already contains the necessary <link> and <script> tags baked into the static markup. In contrast, non-generated routes rely on the injectAssets function to dynamically insert asset references based on the route.assets manifest entry.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →