SXO routes.json Manifest Structure and Asset Injection: A Complete Technical Guide

The SXO framework generates a routes.json manifest during the build process to map URL paths to their server-side modules and client assets, with the production runtime injecting CSS <link> tags before </head> and JavaScript <script type="module"> tags before </body> via the injectAssets utility.

The SXO framework (gc-victor/sxo) relies on a build-time manifest to bridge the gap between esbuild's asset bundling and the server's HTML rendering pipeline. Located at dist/server/routes.json, this manifest describes every page route's server module, entry points, and associated static assets, enabling precise asset injection during both production SSR and development hot-reloading.

Understanding the routes.json Manifest Structure

Each element in the routes.json array describes one page route and contains everything the runtime needs to render it and load its associated client-side resources.

Core Manifest Properties

Every route entry includes the following fields:

  • filename – The path to the server-side JSX module that implements the page.
  • entryPoints – An array of client-side entry files (e.g., client/index.js) that belong to the route.
  • jsx – The relative path that the SSR bundle imports to obtain the page's HTML output.
  • hash – A boolean indicating cache-busting behavior; true in development and false in production.
  • assets – An object containing css and js arrays with client-relative paths of the built files belonging to the route.
  • path (optional) – The URL path derived from the file system that maps to this route.
  • generated (optional) – Set to true by sxo generate for pre-rendered routes, causing the production server to serve static HTML instead of performing SSR.

Build-Time Asset Discovery via esbuild

The manifest generation involves two stages: initial route mapping and asset augmentation. While the initial structure defines the route paths and modules, the asset lists are populated by analyzing esbuild's build output.

The esbuild Metafile Plugin

The esbuild metafile plugin (src/js/esbuild/esbuild-metafile.plugin.js) augments routes.json during the client build. This plugin extracts the list of assets that each route produced from esbuild's metafile and writes them into the assets field of the corresponding entry without modifying the HTML output. The test suite in src/js/esbuild/esbuild-metafile.plugin.test.js verifies this behavior, confirming that the plugin correctly maps output files to their respective routes.

Runtime Asset Injection Process

When the production server handles a request, it loads the manifest and conditionally injects assets into the rendered HTML based on the route configuration.

Loading the Manifest

The server loads the manifest via loadRoutesManifest, implemented in src/js/server/shared/routes-loader.js. This utility includes retry logic for file system operations and validates the manifest structure before returning the route array to the request handler.

The Injection Flow in Production

In src/js/server/prod/core-handler.js, the request handler executes the following sequence for non-generated routes:

  1. Renders the page using the SSR module (module.default || module.jsx).
  2. Checks route.assets and verifies the response is HTML using the isHtml utility.
  3. Calls injectAssets(page, route.assets, normalizedPublicPath) to modify the HTML string before sending the response.

The publicPath value originates from the PUBLIC_PATH configuration and is normalized by normalizePublicPath (exported from src/js/server/utils/inject-assets.js) to ensure consistent path formatting.

The injectAssets Utility

Located in src/js/server/utils/inject-assets.js, the injectAssets function performs the actual DOM manipulation on the HTML string:

  • Inserts <link rel="stylesheet" href="{publicPath}{cssFile}"> before the closing </head> tag.
  • Inserts <script type="module" src="{publicPath}{jsFile}"></script> before the closing </body> tag.

Unit tests in src/js/server/utils/tests/inject-assets.runtime.test.js verify that CSS links appear before </head> and module scripts appear before </body> when given a specific PUBLIC_PATH configuration.

Hot-Reload Asset Handling in Development

During development, SXO uses Server-Sent Events (SSE) to push updates to the browser without full page reloads. The development server sends a payload containing the new HTML body, asset lists, and public path.

Client-Side Asset Reconstruction

The client script (src/js/server/hot-replace.client.js) consumes this payload and reconstructs the asset tags dynamically. The payload structure { body, assets, publicPath } mirrors the production manifest's asset structure, allowing the client to create new <link> and <script> tags with the correct public path prefix. The logic follows the same injection pattern as the server-side utility, ensuring consistency between development and production asset loading.

Summary

  • The manifest at dist/server/routes.json contains per-route metadata including server modules, entry points, and asset references.
  • The esbuild metafile plugin (src/js/esbuild/esbuild-metafile.plugin.js) populates the assets field during the client build by analyzing bundler output.
  • The production server loads routes via loadRoutesManifest and injects assets using injectAssets from src/js/server/utils/inject-assets.js.
  • Asset injection places CSS links before </head> and module scripts before </body>, using the normalized PUBLIC_PATH prefix.
  • Development hot-reload uses the same asset list structure in its SSE payload, with the client script dynamically recreating tags to match the new build output.

Frequently Asked Questions

What is the exact location of the routes.json file in an SXO project?

The build pipeline generates routes.json at dist/server/routes.json relative to the project root. The production server loads this file via loadRoutesManifest from src/js/server/shared/routes-loader.js, which accepts the absolute path to this manifest as its primary argument.

How does SXO determine which CSS and JS files belong to a specific route?

During the client build, the esbuild metafile plugin (src/js/esbuild/esbuild-metafile.plugin.js) analyzes the bundler's output graph to correlate generated assets with their entry points. It then writes these client-relative paths into the assets.css and assets.js arrays of the corresponding route entry in routes.json.

What is the difference between generated and non-generated routes in the manifest?

Routes marked with "generated": true have been pre-rendered to static HTML files using sxo generate. The production server serves these static files directly without executing SSR. Non-generated routes require server-side rendering on each request, triggering the injectAssets flow to dynamically insert the latest CSS and JavaScript into the rendered HTML.

How does the development hot-reload mechanism handle asset injection differently from production?

In development, the server sends asset lists via Server-Sent Events as part of the hot-replace payload ({ body, assets, publicPath }). The client-side script in src/js/server/hot-replace.client.js receives this data and manually constructs new DOM elements to inject styles and scripts, whereas production performs this injection on the server using injectAssets before the HTML reaches the browser.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →