# How `global.css` Gets Injected Across All Routes in SXO

> Learn how SXO automatically injects your global CSS file into every route's entry points for consistent styling. Master your SXO build system.

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

---

**The SXO build system automatically discovers [`src/pages/global.css`](https://github.com/gc-victor/sxo/blob/main/src/pages/global.css) and appends it to every route's entry points, then injects the resulting stylesheet link into the HTML `<head>` at runtime.**

In the `gc-victor/sxo` repository, global styles are shared across every page without requiring manual imports in each component. This automatic injection ensures consistent styling while keeping the developer experience ergonomic.

## Entry Point Discovery and Configuration

The injection process begins during the entry-point discovery phase in [`src/js/esbuild/entry-points-config.js`](https://github.com/gc-victor/sxo/blob/main/src/js/esbuild/entry-points-config.js). The build system specifically looks for a file named [`global.css`](https://github.com/gc-victor/sxo/blob/main/global.css) inside the pages directory.

When found, the relative path is stored and appended to each route's `entryPoints` array:

```javascript
// src/js/esbuild/entry-points-config.js
const rel = `${PAGES_RELATIVE_DIR}/global.css`;   // ← global stylesheet
if (await fileExists(rel)) {
  // Append to each route's entry points
  route.entryPoints.push(rel);
}

```

This ensures that **every route's client bundle** includes the global CSS file as a shared dependency.

## Bundling and Asset Generation

During the esbuild bundling phase, the build receives the list of entry points per route. Because every route now references the same [`global.css`](https://github.com/gc-victor/sxo/blob/main/global.css) file, esbuild deduplicates it and produces a single hashed CSS asset (for example, [`global.XYZ789.css`](https://github.com/gc-victor/sxo/blob/main/global.XYZ789.css)).

The manifest entry for each route is updated in [`src/js/esbuild/esbuild-metafile.plugin.js`](https://github.com/gc-victor/sxo/blob/main/src/js/esbuild/esbuild-metafile.plugin.js) to include this CSS asset in [`route.assets.css`](https://github.com/gc-victor/sxo/blob/main/route.assets.css). This metadata persists across both development and production builds, ensuring the stylesheet is tracked as a dependency for every page.

## Runtime HTML Injection

When a page is rendered—whether in development, production, or static generation—the server calls `injectAssets` from [`src/js/server/utils/inject-assets.js`](https://github.com/gc-victor/sxo/blob/main/src/js/server/utils/inject-assets.js). This utility inserts the CSS link tags before the closing `</head>` tag for every entry listed in [`route.assets.css`](https://github.com/gc-victor/sxo/blob/main/route.assets.css).

The `injectCss` function handles the actual string manipulation:

```javascript
// src/js/server/utils/inject-assets.js
export function injectAssets(html, assets, publicPath) {
  let result = html;
  if (assets.css?.length) result = injectCss(result, assets.css, publicPath);
  if (assets.js?.length)  result = injectJs(result, assets.js, publicPath);
  return result;
}

// injectCss inserts <link> before </head>
export function injectCss(html, css, publicPath) {
  const tags = css.map(
    href => `<link rel="stylesheet" href="${publicPath}${href}">`
  ).join("");
  return html.replace(/<\/head>/i, tags + "</head>");
}

```

Because [`global.css`](https://github.com/gc-victor/sxo/blob/main/global.css) is included in [`route.assets.css`](https://github.com/gc-victor/sxo/blob/main/route.assets.css) for every route, the production handler in [`src/js/server/prod/core-handler.js`](https://github.com/gc-victor/sxo/blob/main/src/js/server/prod/core-handler.js) automatically injects it:

```javascript
// src/js/server/prod/core-handler.js (excerpt)
const normalizedPublicPath = normalizePublicPath(PUBLIC_PATH);
page = injectAssets(page, route.assets, normalizedPublicPath);

```

## Test Verification

Unit tests confirm this behavior at two critical points. The entry-point discovery tests in [`src/js/esbuild/entry-points-config.test.js`](https://github.com/gc-victor/sxo/blob/main/src/js/esbuild/entry-points-config.test.js) verify that each discovered route contains the [`global.css`](https://github.com/gc-victor/sxo/blob/main/global.css) path in its `entryPoints` array. Additionally, runtime tests in [`src/js/server/utils/tests/inject-assets.runtime.test.js`](https://github.com/gc-victor/sxo/blob/main/src/js/server/utils/tests/inject-assets.runtime.test.js) validate that the generated HTML includes the CSS `<link>` tag before the closing `</head>` tag.

## Summary

- **Discovery**: The build system checks for [`src/pages/global.css`](https://github.com/gc-victor/sxo/blob/main/src/pages/global.css) during entry-point configuration and appends it to every route's entry points.
- **Bundling**: esbuild deduplicates the global CSS into a single hashed asset referenced in the route manifest.
- **Injection**: The `injectAssets` utility inserts `<link>` tags into the HTML `<head>` at runtime for every route.
- **Consistency**: This pipeline ensures [`global.css`](https://github.com/gc-victor/sxo/blob/main/global.css) loads on every page without manual imports or configuration.

## Frequently Asked Questions

### Where should I place the global.css file?

Place your [`global.css`](https://github.com/gc-victor/sxo/blob/main/global.css) file directly in the `src/pages/` directory. The build system specifically looks for [`src/pages/global.css`](https://github.com/gc-victor/sxo/blob/main/src/pages/global.css) during the entry-point discovery phase in [`src/js/esbuild/entry-points-config.js`](https://github.com/gc-victor/sxo/blob/main/src/js/esbuild/entry-points-config.js).

### Is the global CSS file deduplicated across routes?

Yes. Since every route includes the same [`global.css`](https://github.com/gc-victor/sxo/blob/main/global.css) entry point, esbuild recognizes the shared dependency and produces a single CSS asset. Each route's manifest references this same hashed file, ensuring browsers cache it efficiently across page navigations.

### Can I disable automatic global CSS injection?

The current implementation in `gc-victor/sxo` does not provide a configuration flag to disable this behavior. To prevent automatic injection, you would need to either remove the [`src/pages/global.css`](https://github.com/gc-victor/sxo/blob/main/src/pages/global.css) file or modify the entry-point discovery logic in [`src/js/esbuild/entry-points-config.js`](https://github.com/gc-victor/sxo/blob/main/src/js/esbuild/entry-points-config.js).

### How does injection differ between development and production?

The injection mechanism remains consistent across environments. Both development and production builds use the same `injectAssets` function from [`src/js/server/utils/inject-assets.js`](https://github.com/gc-victor/sxo/blob/main/src/js/server/utils/inject-assets.js) to place CSS links before the `</head>` tag. The primary difference lies in asset hashing and caching headers, not in the injection logic itself.