# How Per-Route Client Entries Work with the `clientDir` Configuration in SXO

> Learn how SXO utilizes per-route client entries with the clientDir configuration to enable custom client-side bundles for each page during the esbuild pipeline.

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

---

**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`](https://github.com/gc-victor/sxo/blob/main/sxo.config.json) file. The resolved value is exported as `CLIENT_DIR` from [`src/js/constants.js`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/src/js/constants.js) (lines 14-15):

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

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

```javascript
// 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`](https://github.com/gc-victor/sxo/blob/main/global.css) (line 47)

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

```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`](https://github.com/gc-victor/sxo/blob/main/custom.index.jsx) as a valid client entry due to the `*.index.*` pattern. No code changes are required beyond the configuration file.

## Summary

- The **`clientDir`** configuration (default: `client`) defines the subdirectory name where SXO searches for per-route client entries.
- **[`src/js/constants.js`](https://github.com/gc-victor/sxo/blob/main/src/js/constants.js)** exports the resolved `CLIENT_DIR` value used throughout the build pipeline.
- **`scanPagesTree()`** excludes directories matching `CLIENT_DIR` from the pages tree to prevent routing conflicts.
- **`findClientEntries()`** discovers files matching `index.*` or `*.index.*` inside each page's `<clientDir>` folder.
- **`assembleRoutes()`** compiles these entries into esbuild `entryPoints`, 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`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/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.