How Per-Route Client Entries Work with the `clientDir` Configuration in SXO
SXO allows each page to have its own client-side bundle (per-route client entry) located in a configurable subdirectory, defaulting to client/, which the build system discovers automatically during the esbuild pipeline.
The clientDir setting controls where SXO looks for page-specific JavaScript and CSS. You can define it via the --client-dir CLI flag, the CLIENT_DIR environment variable, or the sxo.config.json file. The resolved value is exported as CLIENT_DIR from src/js/constants.js and consumed throughout the build pipeline to isolate client code from page markup.
Understanding the clientDir Configuration
The configuration resolution happens in src/js/config.js, which validates that clientDir is a single-segment, non-empty string (lines 453-454). Once validated, the value is exposed through src/js/constants.js (lines 14-15):
// src/js/constants.js
export const CLIENT_DIR = process.env.CLIENT_DIR || 'client';
This constant drives the discovery logic in the esbuild entry-points configuration.
How Per-Route Client Entries Are Discovered
The build process in src/js/esbuild/entry-points-config.js orchestrates three distinct phases to map pages to their client bundles.
Scanning the Pages Directory
The scanPagesTree() function walks the pages/ tree but explicitly skips any directory whose name matches CLIENT_DIR (lines 61-64). This prevents the client subdirectory from being treated as a routable page:
// src/js/esbuild/entry-points-config.js
if (entry.name === CLIENT_DIR) {
continue;
}
Locating Client Entry Files
For each valid page directory containing an index.* file, findClientEntries() looks inside <page-dir>/<clientDir> (line 96). It returns every file matching either index.* or *.index.* (lines 11-14):
// Pattern matching logic
const clientEntryRegex = /^(index\..*|.*\.index\..*)$/;
These matches become the per-route client entries.
Assembling Route Objects
The assembleRoutes() function constructs the final route description consumed by esbuild. The entryPoints array for each route includes:
- All client entry paths resolved relative to the current working directory (line 47)
- An optional global
global.css(line 47)
// src/js/esbuild/entry-points-config.js
const entryPoints = [
...(clientEntries || []),
globalCssPath,
].filter(Boolean);
Each entry point generates a separate bundle that the SXO runtime later injects into the generated HTML for that specific route.
Configuration Examples
Default clientDir (client)
With the default configuration, place client code in a client subdirectory:
pages/
└─ blog/
├─ index.tsx // page markup
└─ client/
└─ index.ts // client bundle for /blog
Running sxo dev or sxo build detects pages/blog/client/index.ts as the per-route client entry for the /blog route.
Custom clientDir (assets) via Config
Define a custom directory name in sxo.config.json:
{
"clientDir": "assets"
}
Update your directory structure:
pages/
└─ shop/
├─ index.jsx
└─ assets/
└─ custom.index.jsx
Now findClientEntries() searches inside pages/shop/assets/ and recognizes custom.index.jsx as a valid client entry due to the *.index.* pattern. No code changes are required beyond the configuration file.
Summary
- The
clientDirconfiguration (default:client) defines the subdirectory name where SXO searches for per-route client entries. src/js/constants.jsexports the resolvedCLIENT_DIRvalue used throughout the build pipeline.scanPagesTree()excludes directories matchingCLIENT_DIRfrom the pages tree to prevent routing conflicts.findClientEntries()discovers files matchingindex.*or*.index.*inside each page's<clientDir>folder.assembleRoutes()compiles these entries into esbuildentryPoints, generating isolated bundles for each route.
Frequently Asked Questions
What happens if I don't specify a clientDir?
If you omit the configuration, SXO defaults to client. The build system looks for a client/ subdirectory inside each page folder to locate per-route entry files. This default is hardcoded in src/js/constants.js as a fallback when the environment variable or config file does not override it.
Can I use nested directories inside the clientDir folder?
No. The discovery logic in findClientEntries() specifically looks for files directly inside <page-dir>/<clientDir> and does not recurse into subdirectories. Only immediate children matching the index.* or *.index.* patterns are considered valid per-route client entries.
Why does SXO skip directories named clientDir during page scanning?
The scanPagesTree() function intentionally skips any directory matching CLIENT_DIR to prevent the client code directory from being registered as a routable page. If the system did not skip these directories, requests to /blog/client/ would resolve as a valid route instead of being treated as static assets belonging to the /blog page.
How do I migrate from the default client directory to a custom name?
Update your sxo.config.json with the new clientDir value, then rename every client/ subdirectory in your pages/ tree to match. Because the build system relies solely on the configured name to discover entry points, no import statements or page logic require modification. Restart the dev server or rerun the build to pick up the new structure.
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 →