# Generated vs Non-Generated Routes in SXO Production: A Technical Comparison

> Understand generated vs non-generated routes in SXO production. Learn how generated routes offer static HTML with caching, while non-generated routes provide dynamic server-side rendering for every request.

- Repository: [Víctor García/sxo](https://github.com/gc-victor/sxo)
- Tags: technical-comparison
- Published: 2026-03-02

---

**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`](https://github.com/gc-victor/sxo/blob/main/dist/server/routes.json) during the build process. The `loadRoutesManifest` function in [`src/js/server/shared/routes-loader.js`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/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:

```json
// 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`](https://github.com/gc-victor/sxo/blob/main/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).

```javascript
// 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:

```javascript
// 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.

```javascript
// 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`](https://github.com/gc-victor/sxo/blob/main/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: true` in 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 carry `max-age=0` cache headers to prevent HTML caching.
- The `createGeneratedRouteHandler` function in production adapters intercepts marker responses to serve static files, while non-generated flows use the core SSR handler in [`src/js/server/prod/core-handler.js`](https://github.com/gc-victor/sxo/blob/main/src/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`](https://github.com/gc-victor/sxo/blob/main/src/js/cli/commands/generate.js), which pre-renders the specified routes to static HTML files and updates [`dist/server/routes.json`](https://github.com/gc-victor/sxo/blob/main/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.