SXO is a multi-runtime server-side JSX tool for Node.js, Bun, Deno, and Cloudflare Workers. It includes SXOUI, a framework-agnostic UI library inspired by shadcn/ui
See the codeA fast, minimal architecture convention and CLI for building websites with server‑side JSX. No React, no client framework, just composable JSX optimized for the server, a clean directory-based router, hot replacement, and powered by esbuild plus a Rust JSX transformer.
Multi-Platform Library: SXO runs seamlessly across Node.js, Bun, Deno, and Cloudflare Workers. The CLI automatically detects your runtime and loads the optimized adapter, while providing a consistent development and production experience across all platforms.
index.(jsx|tsx)).<html>, <head>, and <body>.--loaders ".svg=file" --loaders ".ts=tsx").--public-path), env (PUBLIC_PATH), or config; empty string "" allowed for relative URLs.SXO is designed as a truly multi-runtime library that runs seamlessly across different JavaScript runtimes:
The SXO CLI automatically detects your JavaScript runtime and loads the appropriate platform adapter:
# Same command works everywhere
npx sxo dev # Development server
npx sxo start # Production server
Runtime Detection: The CLI checks globalThis.Bun and globalThis.Deno to identify the current platform, falling back to Node.js. Each adapter uses platform-native APIs (e.g., Bun.serve(), Deno.serve(), http.createServer()) for optimal performance.
Shared Core Logic: All adapters share the same Web Standard-based core (Request/Response), ensuring consistent behavior across platforms. Routing, SSR, static file serving, and middleware execution work identically everywhere.
Cloudflare Workers: Due to its unique environment, Cloudflare Workers requires a custom entry point using the sxo/cloudflare export and a factory pattern (see Platform Adapters).
Model
src) containing:
components directory with JSX componentsutils directory with utility functionsmiddleware.js defining user middleware chainsrc/pages) containing:
global.css (optional)index.(tsx|jsx)index.* page file becomes a route.<clientDir>/index.(ts|tsx|js|jsx) inside that route directory is added as a client entry (default clientDir is "client"; precedence: .ts > .tsx > .js > .jsx).global.css, if present, is added as a shared stylesheet entry for every route.dist/clientdist/serverdist/server/routes.jsonAliases Available in both client & server builds:
@components -> src/components
@pages -> src/pages
@utils -> src/utils
Install & run (no install needed if using npx):
npx sxo dev
or
pnpm dlx sxo dev
Example structure:
your-app
├── src
│ ├── middleware.js
│ ├── components
│ │ ├── Page.jsx
│ │ └── Header.jsx
│ └── pages
│ ├── global.css
│ ├── index.jsx
│ └── about
│ ├── index.jsx
│ └── client
│ └── index.js
└── package.json
Example component:
// src/components/Page.jsx
export function Page({ children }) {
return <div className="page">{children}</div>;
}
Example page:
// src/pages/index.jsx
import { Header } from "@components/Header.js";
export default () => (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<title>Home</title>
</head>
<body>
<Header title="Home" />
<p>Welcome to SXO.</p>
</body>
</html>
);
Commands:
sxo create <project> # Create a new SXO project from templates (prompts for runtime; defaults to node)
sxo add <component> # Add a component from the basecoat library to src/components
sxo dev # Start the development server with hot replace
sxo build # Build the project for production (client and server bundles)
sxo start # Start the production server to serve built output
sxo clean # Remove the output directory (clean build artifacts)
sxo generate # Pre-render static routes to HTML after a successful build
Create a new project:
# Create a new project in a new directory
sxo create my-app
# Create a project in the current directory
sxo create .
# Or omit the name entirely (uses current directory name)
sxo create
Runtime Selection:
When you run the command, you'll see an interactive prompt to select your target runtime:
Select a runtime:
1) node (default)
2) bun
3) deno
4) workers
>
node (default)1 → selects node2 → selects bun3 → selects deno4 → selects workers (Cloudflare Workers)In non-interactive environments (CI, tests), the prompt is skipped and node is used automatically.
Templates are fetched from the gc-victor/sxo repository under templates/<runtime>/....
Existing Directory:
If the target directory already exists, you'll be prompted to confirm overwriting:
Create SXO template in "my-app"? (This will overwrite existing files.) (y/N)
Full Example Workflow:
# 1. Create the project
sxo create my-app
# 2. Select runtime when prompted (or press Enter for node)
# > 2 (selects bun)
# 3. Follow the next steps printed by the CLI
cd my-app
pnpm install
pnpm run dev
Add components from SXOUI:
# Add a button component
sxo add button
# Add a dialog component
sxo add dialog
# Components are installed to src/components/
# Browse all available components at https://sxoui.com
SXOUI Component Library: SXO includes access to 25+ production-ready components via the sxo add command. Visit sxoui.com to browse the complete component library with live demos, accessibility documentation, and copy-paste ready code examples.
Point to a different pages directory:
sxo build --pages-dir examples/node/src/pages
sxo start --pages-dir examples/node/src/pages --port 4011
Configure custom esbuild loaders for the server build:
# Via CLI flags (repeatable or comma-separated)
sxo dev --loaders ".svg=file" --loaders ".ts=tsx"
sxo build --loaders "svg=file,ts=tsx"
# Via environment variable (JSON format)
LOADERS='{"svg":"file",".ts":"tsx"}' sxo dev
# Via config file (sxo.config.json or sxo.config.js)
{
"loaders": {
".svg": "file",
".ts": "tsx"
}
}
🎨 sxoui.com — Production-ready components for SXO
SXOUI is a comprehensive component library built specifically for SXO, featuring 25+ accessible, semantic, and performant components that work with server-side rendering and optional client-side interactivity.
# Install individual components
sxo add button
sxo add card
sxo add dialog
# Components are added to src/components/
Visit sxoui.com for:
// src/pages/index.jsx
import Button from "@components/button.jsx";
import Card from "@components/card.jsx";
export default () => (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<title>My App</title>
</head>
<body>
<Card>
<Card.Header>
<Card.Title>Welcome</Card.Title>
</Card.Header>
<Card.Content>
<p>Get started with SXOUI components</p>
</Card.Content>
<Card.Footer>
<Button variant="primary">Get Started</Button>
</Card.Footer>
</Card>
</body>
</html>
);
Demonstrates SXO with Node.js.
Location: examples/node
Features:
Quickstart:
cd examples/node
pnpm install
pnpm run dev
A full example demonstrating SXO with Cloudflare Workers, including dynamic routes, per‑route client entries, global HTML/CSS, and deployment via Wrangler.
Location: examples/workers
Features:
[slug] segmentssrc/pages/<route>/client/index.js)src/componentssrc/pages/global.css)Quickstart:
cd examples/workers
pnpm i
# SXO dev server (SSE hot replace)
pnpm dev
# Optional: run Worker locally in another terminal
pnpm start # wrangler dev
# Build (triggers postbuild import generation)
pnpm build
# Deploy to Cloudflare Workers
pnpm deploy
Structure:
examples/workers/
├── scripts/
│ ├── generate-imports.js
│ └── index.js
├── src/
│ ├── components/
│ │ ├── Header.jsx
│ │ └── Page.jsx
│ └── pages/
│ ├── global.css
│ ├── index.jsx
│ ├── about/
│ │ ├── index.jsx
│ │ └── [slug]/index.jsx
│ └── counter/
│ ├── index.jsx
│ ├── counter.jsx
│ └── client/index.js
├── sxo.config.js
├── wrangler.jsonc
└── vitest.config.js
Demonstrates SXO with Bun's high-performance runtime.
Location: examples/bun
Quickstart:
cd examples/bun
pnpm install
pnpm run dev
Demonstrates SXO with Deno's secure runtime.
Location: examples/deno
Quickstart:
cd examples/deno
pnpm install
pnpm run dev
SXO supports multiple deployment platforms beyond the standard CLI server. Adapters share the same core runtime and use Web Standard APIs (Request, Response).
For running on Node.js.
npx sxo start
The CLI automatically detects the runtime and loads the optimized adapter.
For high-performance serving with Bun.
bunx sxo start
The CLI automatically detects the runtime and loads the optimized adapter.
For running on Deno.
deno run -A npm:sxo start
The CLI automatically detects the runtime and loads the optimized adapter.
Requires configuring wrangler.jsonc aliases to point to your build artifacts.
wrangler.jsonc:
{
"alias": {
"sxo:routes": "./dist/server/routes.json",
"sxo:modules": "./dist/server/modules.js",
},
}
worker.js:
import { createHandler } from "sxo/cloudflare";
import middleware from "./src/middleware.js";
export default await createHandler({
publicPath: "/",
middleware,
});
A route exists when a directory contains an index.(tsx|jsx|ts|js) file.
Static example:
src/pages/
├── index.jsx -> "/"
├── about/
│ └── index.jsx -> "/about"
└── contact/
└── index.jsx -> "/contact"
Dynamic segments: directories named with bracket notation (e.g., [slug], [category]). Multiple dynamic parameters are supported in a single route.
src/pages/blog/[slug]/index.jsx -> /blog/:slug
src/pages/shop/[category]/[product]/index.jsx -> /shop/:category/:product
src/pages/users/[userId]/profile/index.jsx -> /users/:userId/profile
Parameters object passed to the page render function is shaped from bracket names:
{ slug: string }{ category: string, product: string }Parameter naming rules:
[id], [userId], [post_id], [category123]A page module can export:
| Export | Type | Required | Description |
|---|---|---|---|
default | (params) => JSX or string | Yes* | Page render function |
Note: Pages must return a full <html>...</html> document (including <head> and <body>). The separate head export is no longer supported.
SXO supports two middleware signatures depending on your runtime.
sxo dev, sxo start)Uses Node-style middleware.
Signature: (req, res, next) => void or (req, res) => boolean
// src/middleware.js
export default function (req, res, next) {
if (req.url === "/ping") {
res.end("pong");
return; // Handled
}
next(); // Continue
}
createHandler)Uses Web Standard middleware. This is required when using sxo/cloudflare or the internal runtime adapters.
Signature: (request: Request, env: object) => Response | void
// src/middleware.js
export default function (request, env) {
const url = new URL(request.url);
if (url.pathname === "/ping") {
return new Response("pong");
}
// Return nothing to continue
}
Use cases:
Mechanism:
/hot-replace?href=<current_path>/hot-replace.js) receives a JSON payload ({ body, assets, publicPath }) and performs partial replacement:
<body> innerHTML.body field of the payload.Readiness Probe:
HEAD first, falls back to GET< 500 (including 404) counts as "ready"PAGES_DIR/404.(tsx|jsx|ts|js) and PAGES_DIR/500.(tsx|jsx|ts|js)jsx; pages must return a full <html> document and include their own <head>sxo generate; no route asset mappings and no runtime asset injection (include any required CSS/JS inside the returned document).public, max-age=0, must-revalidate; 500 → no-store.<!doctype html>.After sxo build (or dev prebuild):
dist/
├── client/ # public assets: html, js, css
└── server/ # private SSR bundles
└── routes.json # routes manifest and metadata
routes.json entries (one per route; includes per‑route assets):
[
{
"filename": "about/index.html",
"entryPoints": ["src/pages/about/client/index.js", "src/pages/global.css"],
"jsx": "src/pages/about/index.jsx",
"scriptLoading": "module",
"hash": false,
"path": "about",
"generated": false
}
]
Fields:
filename relative to dist/cliententryPoints (per‑route client entries and global.css if present)jsx source page module relative pathhash boolean (true in dev for cache-busting semantics)path (omitted for root route)generated boolean; if true, the production server serves the built HTML as-is (skips SSR) with Cache-Control: public, max-age=300. Non-generated/dynamic pages are served with Cache-Control: public, max-age=0, must-revalidate.Manifest Reuse:
jsx file still exists and no new route index.* appeared, the existing manifest is reused with global.css refreshed.The generate workflow lets you pre-render static routes to HTML after a successful build and have the production server serve those pages as-is (skipping SSR).
sxo generate after sxo build.[slug] segments) are generated.dist/server/routes.json.jsx) to return a full <html> document, injects built assets from route.assets (PUBLIC_PATH normalized), and prepares the final HTML.dist/client/<route>/index.html.generated: true for that route in the manifest.generated: true.routes.json is not present, run sxo build first.Production server behavior:
generated: true, the server sends the built HTML directly (no SSR) with Cache-Control: public, max-age=300.route.assets (PUBLIC_PATH normalized), and responds with Cache-Control: public, max-age=0, must-revalidate.Notes:
[param]) are never generated.generated flag is persisted to dist/server/routes.json.module.default || module.jsx.Pages must return a full <html>...</html> document (including <head> and <body>).
global.css (optional) is included as a client entry for all routes when present. Recommended for shared styles.
Example:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- Head contents are authored directly by pages -->
</head>
<body>
<main>...</main>
</body>
</html>
Production & dev servers serve from dist/client only.
Features:
dist/client.public, max-age=31536000, immutable).public, max-age=300)..br > .gz variant if client supports & asset is compressible.../ style attempts rejected (403/404).Hashed filename detection heuristics: segment containing 8+ hex chars or an 8-char base36-ish uppercase segment before next dot.
Precedence:
CLI Flags > sxo.config.* > .env / .env.local > defaults
Command defaults:
open=trueopen=falseopen=falseExample sxo.config.json:
{
"port": 4000,
"pagesDir": "src/pages",
"outDir": "dist",
"open": false,
"build": {
"minify": false,
"sourcemap": "inline"
},
"loaders": { ".svg": "file" }
}
The build property accepts any esbuild client configuration options for the client build only. Defaults applied by SXO for the client build are: minify: true, sourcemap: isDev ? "inline" : false.
Note: The server build uses its own hardcoded defaults:
minify: true,sourcemap: false. These are not affected by thebuildproperty.
Explicit Flag Detection:
Flags only override file/env/default if explicitly passed (e.g. --open, --no-open, --open=false). Inferred / defaulted flags are filtered out (see prepareFlags()).
Key flags:
--port # Port to run the server (dev/start). Default: 3000. Example: --port 4000
--pages-dir # Path to the pages directory (default: src/pages)
--out-dir # Output directory for build artifacts (default: dist)
--open / --no-open # Auto-open the browser when the dev server is ready (toggle)
--public-path <path> # Public base URL for emitted asset URLs (default: "/"); empty string "" allowed for relative paths
--client-dir <name> # Subdirectory name for per-route client entry (default: client)
--loaders <ext=loader> # esbuild server loaders (dev/build only; repeatable or comma-separated). Example: --loaders ".svg=file" --loaders ".ts=tsx"
--verbose # Enable verbose logging for debugging and diagnostics
--no-color # Disable ANSI/colorized log output (useful for CI)
--config <file> # Load an alternate config file (e.g., sxo.config.json or .js)
Loaded (non-destructively) from .env then .env.local unless already set.
Recognized:
| Variable | Meaning | Default |
|---|---|---|
| PORT | Port | 3000 |
| PAGES_DIR | Pages directory | src/pages |
| OUTPUT_DIR | Base output directory | dist |
| OPEN | Auto-open dev browser | true (dev) |
| PUBLIC_PATH | Public base URL for asset URLs (esbuild publicPath). Empty string "" allowed and preserved. | "/" |
| CLIENT_DIR | Per-route client entry subdirectory name | client |
| LOADERS | esbuild server loaders as JSON map (e.g., {"svg":"file"} or {".ts":"tsx"}) | (unset) |
| VERBOSE | Verbose logging | false |
| NO_COLOR | Disable colorized output | (unset) |
| HEADER_TIMEOUT_MS | Node headers timeout in ms (server.headersTimeout). Set a non-negative integer to override; unset to use Node default. | (unset) |
| REQUEST_TIMEOUT_MS | Request timeout in ms (server.requestTimeout). | 120000 |
Derived / injected:
| Variable | Meaning |
|---|---|
| OUTPUT_DIR_CLIENT | <outDir>/client |
| OUTPUT_DIR_SERVER | <outDir>/server |
| SXO_RESOLVED_CONFIG | JSON blob of resolved config |
| DEV | "true" in dev command, else "false" |
| SXO_COMMAND | Current command (`dev |
| BUILD | Custom esbuild client config object propagated to child build process (only in dev/build) |
| LOADERS | esbuild server loaders map propagated to child build process (only in dev/build) |
| PUBLIC_PATH | Public base URL for assets propagated to the build (defaults to "/" when unset; empty string preserved) |
| CLIENT_DIR | Configured per-route client entry subdirectory name |
SXO includes a Rust/WASM JSX transformer that transforms JSX into template literals with small runtime helpers.
What it does:
className → class, htmlFor → for, SVG/camelCase to kebab where applicable) and supports spread props.map, flatMap, filter, reduce, slice, concat, flat, modern array copies, and forEach) with ${__jsxList(...)}
so list output is safely joined into a single string.Runtime helpers:
__jsxComponent(Component, propsArrayOrObject, children?) → renders components to string (props objects in arrays are merged).__jsxSpread(obj) → serializes element attributes from an object (boolean true becomes a valueless attribute).__jsxList(value) → joins arrays into a string; returns "" for null/undefined; passes through non-array values.SXO provides foundational security controls while giving you full control over application-specific security policies through middleware.
✅ What SXO Provides:
X-Content-Type-Options, X-Frame-Options, Referrer-PolicyescapeHtml())^[A-Za-z0-9._-]{1,200}$)❌ What You Must Implement:
See SECURITY.md for detailed guidance on:
Before deploying to production:
.env / .env.local, never committed to gitnpm audit / pnpm audit)Do not open public issues for security vulnerabilities.
Please report security issues responsibly via GitHub Security Advisories or by contacting the maintainers privately. See SECURITY.md for details.
Run all tests:
pnpm test
Focused suites:
Typical flow:
sxo build
sxo start --port 3000
Serve behind a reverse proxy (optional). Add your own middleware for:
pnpm ipnpm testfeat:, fix:, etc.).README.md / AGENTS.md) when altering behavior (manifest shape, routing semantics, middleware contract).MIT — see LICENSE.
Looking for chat? Open an issue to propose a community space if demand emerges.
226 commits
JavaScript
86.4%
Rust
10.2%
CSS
1.8%
SXO is a multi-runtime server-side JSX tool for Node.js, Bun, Deno, and Cloudflare Workers. It includes SXOUI, a framework-agnostic UI library inspired by shadcn/ui
See the codeA fast, minimal architecture convention and CLI for building websites with server‑side JSX. No React, no client framework, just composable JSX optimized for the server, a clean directory-based router, hot replacement, and powered by esbuild plus a Rust JSX transformer.
Multi-Platform Library: SXO runs seamlessly across Node.js, Bun, Deno, and Cloudflare Workers. The CLI automatically detects your runtime and loads the optimized adapter, while providing a consistent development and production experience across all platforms.
index.(jsx|tsx)).<html>, <head>, and <body>.--loaders ".svg=file" --loaders ".ts=tsx").--public-path), env (PUBLIC_PATH), or config; empty string "" allowed for relative URLs.SXO is designed as a truly multi-runtime library that runs seamlessly across different JavaScript runtimes:
The SXO CLI automatically detects your JavaScript runtime and loads the appropriate platform adapter:
# Same command works everywhere
npx sxo dev # Development server
npx sxo start # Production server
Runtime Detection: The CLI checks globalThis.Bun and globalThis.Deno to identify the current platform, falling back to Node.js. Each adapter uses platform-native APIs (e.g., Bun.serve(), Deno.serve(), http.createServer()) for optimal performance.
Shared Core Logic: All adapters share the same Web Standard-based core (Request/Response), ensuring consistent behavior across platforms. Routing, SSR, static file serving, and middleware execution work identically everywhere.
Cloudflare Workers: Due to its unique environment, Cloudflare Workers requires a custom entry point using the sxo/cloudflare export and a factory pattern (see Platform Adapters).
Model
src) containing:
components directory with JSX componentsutils directory with utility functionsmiddleware.js defining user middleware chainsrc/pages) containing:
global.css (optional)index.(tsx|jsx)index.* page file becomes a route.<clientDir>/index.(ts|tsx|js|jsx) inside that route directory is added as a client entry (default clientDir is "client"; precedence: .ts > .tsx > .js > .jsx).global.css, if present, is added as a shared stylesheet entry for every route.dist/clientdist/serverdist/server/routes.jsonAliases Available in both client & server builds:
@components -> src/components
@pages -> src/pages
@utils -> src/utils
Install & run (no install needed if using npx):
npx sxo dev
or
pnpm dlx sxo dev
Example structure:
your-app
├── src
│ ├── middleware.js
│ ├── components
│ │ ├── Page.jsx
│ │ └── Header.jsx
│ └── pages
│ ├── global.css
│ ├── index.jsx
│ └── about
│ ├── index.jsx
│ └── client
│ └── index.js
└── package.json
Example component:
// src/components/Page.jsx
export function Page({ children }) {
return <div className="page">{children}</div>;
}
Example page:
// src/pages/index.jsx
import { Header } from "@components/Header.js";
export default () => (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<title>Home</title>
</head>
<body>
<Header title="Home" />
<p>Welcome to SXO.</p>
</body>
</html>
);
Commands:
sxo create <project> # Create a new SXO project from templates (prompts for runtime; defaults to node)
sxo add <component> # Add a component from the basecoat library to src/components
sxo dev # Start the development server with hot replace
sxo build # Build the project for production (client and server bundles)
sxo start # Start the production server to serve built output
sxo clean # Remove the output directory (clean build artifacts)
sxo generate # Pre-render static routes to HTML after a successful build
Create a new project:
# Create a new project in a new directory
sxo create my-app
# Create a project in the current directory
sxo create .
# Or omit the name entirely (uses current directory name)
sxo create
Runtime Selection:
When you run the command, you'll see an interactive prompt to select your target runtime:
Select a runtime:
1) node (default)
2) bun
3) deno
4) workers
>
node (default)1 → selects node2 → selects bun3 → selects deno4 → selects workers (Cloudflare Workers)In non-interactive environments (CI, tests), the prompt is skipped and node is used automatically.
Templates are fetched from the gc-victor/sxo repository under templates/<runtime>/....
Existing Directory:
If the target directory already exists, you'll be prompted to confirm overwriting:
Create SXO template in "my-app"? (This will overwrite existing files.) (y/N)
Full Example Workflow:
# 1. Create the project
sxo create my-app
# 2. Select runtime when prompted (or press Enter for node)
# > 2 (selects bun)
# 3. Follow the next steps printed by the CLI
cd my-app
pnpm install
pnpm run dev
Add components from SXOUI:
# Add a button component
sxo add button
# Add a dialog component
sxo add dialog
# Components are installed to src/components/
# Browse all available components at https://sxoui.com
SXOUI Component Library: SXO includes access to 25+ production-ready components via the sxo add command. Visit sxoui.com to browse the complete component library with live demos, accessibility documentation, and copy-paste ready code examples.
Point to a different pages directory:
sxo build --pages-dir examples/node/src/pages
sxo start --pages-dir examples/node/src/pages --port 4011
Configure custom esbuild loaders for the server build:
# Via CLI flags (repeatable or comma-separated)
sxo dev --loaders ".svg=file" --loaders ".ts=tsx"
sxo build --loaders "svg=file,ts=tsx"
# Via environment variable (JSON format)
LOADERS='{"svg":"file",".ts":"tsx"}' sxo dev
# Via config file (sxo.config.json or sxo.config.js)
{
"loaders": {
".svg": "file",
".ts": "tsx"
}
}
🎨 sxoui.com — Production-ready components for SXO
SXOUI is a comprehensive component library built specifically for SXO, featuring 25+ accessible, semantic, and performant components that work with server-side rendering and optional client-side interactivity.
# Install individual components
sxo add button
sxo add card
sxo add dialog
# Components are added to src/components/
Visit sxoui.com for:
// src/pages/index.jsx
import Button from "@components/button.jsx";
import Card from "@components/card.jsx";
export default () => (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<title>My App</title>
</head>
<body>
<Card>
<Card.Header>
<Card.Title>Welcome</Card.Title>
</Card.Header>
<Card.Content>
<p>Get started with SXOUI components</p>
</Card.Content>
<Card.Footer>
<Button variant="primary">Get Started</Button>
</Card.Footer>
</Card>
</body>
</html>
);
Demonstrates SXO with Node.js.
Location: examples/node
Features:
Quickstart:
cd examples/node
pnpm install
pnpm run dev
A full example demonstrating SXO with Cloudflare Workers, including dynamic routes, per‑route client entries, global HTML/CSS, and deployment via Wrangler.
Location: examples/workers
Features:
[slug] segmentssrc/pages/<route>/client/index.js)src/componentssrc/pages/global.css)Quickstart:
cd examples/workers
pnpm i
# SXO dev server (SSE hot replace)
pnpm dev
# Optional: run Worker locally in another terminal
pnpm start # wrangler dev
# Build (triggers postbuild import generation)
pnpm build
# Deploy to Cloudflare Workers
pnpm deploy
Structure:
examples/workers/
├── scripts/
│ ├── generate-imports.js
│ └── index.js
├── src/
│ ├── components/
│ │ ├── Header.jsx
│ │ └── Page.jsx
│ └── pages/
│ ├── global.css
│ ├── index.jsx
│ ├── about/
│ │ ├── index.jsx
│ │ └── [slug]/index.jsx
│ └── counter/
│ ├── index.jsx
│ ├── counter.jsx
│ └── client/index.js
├── sxo.config.js
├── wrangler.jsonc
└── vitest.config.js
Demonstrates SXO with Bun's high-performance runtime.
Location: examples/bun
Quickstart:
cd examples/bun
pnpm install
pnpm run dev
Demonstrates SXO with Deno's secure runtime.
Location: examples/deno
Quickstart:
cd examples/deno
pnpm install
pnpm run dev
SXO supports multiple deployment platforms beyond the standard CLI server. Adapters share the same core runtime and use Web Standard APIs (Request, Response).
For running on Node.js.
npx sxo start
The CLI automatically detects the runtime and loads the optimized adapter.
For high-performance serving with Bun.
bunx sxo start
The CLI automatically detects the runtime and loads the optimized adapter.
For running on Deno.
deno run -A npm:sxo start
The CLI automatically detects the runtime and loads the optimized adapter.
Requires configuring wrangler.jsonc aliases to point to your build artifacts.
wrangler.jsonc:
{
"alias": {
"sxo:routes": "./dist/server/routes.json",
"sxo:modules": "./dist/server/modules.js",
},
}
worker.js:
import { createHandler } from "sxo/cloudflare";
import middleware from "./src/middleware.js";
export default await createHandler({
publicPath: "/",
middleware,
});
A route exists when a directory contains an index.(tsx|jsx|ts|js) file.
Static example:
src/pages/
├── index.jsx -> "/"
├── about/
│ └── index.jsx -> "/about"
└── contact/
└── index.jsx -> "/contact"
Dynamic segments: directories named with bracket notation (e.g., [slug], [category]). Multiple dynamic parameters are supported in a single route.
src/pages/blog/[slug]/index.jsx -> /blog/:slug
src/pages/shop/[category]/[product]/index.jsx -> /shop/:category/:product
src/pages/users/[userId]/profile/index.jsx -> /users/:userId/profile
Parameters object passed to the page render function is shaped from bracket names:
{ slug: string }{ category: string, product: string }Parameter naming rules:
[id], [userId], [post_id], [category123]A page module can export:
| Export | Type | Required | Description |
|---|---|---|---|
default | (params) => JSX or string | Yes* | Page render function |
Note: Pages must return a full <html>...</html> document (including <head> and <body>). The separate head export is no longer supported.
SXO supports two middleware signatures depending on your runtime.
sxo dev, sxo start)Uses Node-style middleware.
Signature: (req, res, next) => void or (req, res) => boolean
// src/middleware.js
export default function (req, res, next) {
if (req.url === "/ping") {
res.end("pong");
return; // Handled
}
next(); // Continue
}
createHandler)Uses Web Standard middleware. This is required when using sxo/cloudflare or the internal runtime adapters.
Signature: (request: Request, env: object) => Response | void
// src/middleware.js
export default function (request, env) {
const url = new URL(request.url);
if (url.pathname === "/ping") {
return new Response("pong");
}
// Return nothing to continue
}
Use cases:
Mechanism:
/hot-replace?href=<current_path>/hot-replace.js) receives a JSON payload ({ body, assets, publicPath }) and performs partial replacement:
<body> innerHTML.body field of the payload.Readiness Probe:
HEAD first, falls back to GET< 500 (including 404) counts as "ready"PAGES_DIR/404.(tsx|jsx|ts|js) and PAGES_DIR/500.(tsx|jsx|ts|js)jsx; pages must return a full <html> document and include their own <head>sxo generate; no route asset mappings and no runtime asset injection (include any required CSS/JS inside the returned document).public, max-age=0, must-revalidate; 500 → no-store.<!doctype html>.After sxo build (or dev prebuild):
dist/
├── client/ # public assets: html, js, css
└── server/ # private SSR bundles
└── routes.json # routes manifest and metadata
routes.json entries (one per route; includes per‑route assets):
[
{
"filename": "about/index.html",
"entryPoints": ["src/pages/about/client/index.js", "src/pages/global.css"],
"jsx": "src/pages/about/index.jsx",
"scriptLoading": "module",
"hash": false,
"path": "about",
"generated": false
}
]
Fields:
filename relative to dist/cliententryPoints (per‑route client entries and global.css if present)jsx source page module relative pathhash boolean (true in dev for cache-busting semantics)path (omitted for root route)generated boolean; if true, the production server serves the built HTML as-is (skips SSR) with Cache-Control: public, max-age=300. Non-generated/dynamic pages are served with Cache-Control: public, max-age=0, must-revalidate.Manifest Reuse:
jsx file still exists and no new route index.* appeared, the existing manifest is reused with global.css refreshed.The generate workflow lets you pre-render static routes to HTML after a successful build and have the production server serve those pages as-is (skipping SSR).
sxo generate after sxo build.[slug] segments) are generated.dist/server/routes.json.jsx) to return a full <html> document, injects built assets from route.assets (PUBLIC_PATH normalized), and prepares the final HTML.dist/client/<route>/index.html.generated: true for that route in the manifest.generated: true.routes.json is not present, run sxo build first.Production server behavior:
generated: true, the server sends the built HTML directly (no SSR) with Cache-Control: public, max-age=300.route.assets (PUBLIC_PATH normalized), and responds with Cache-Control: public, max-age=0, must-revalidate.Notes:
[param]) are never generated.generated flag is persisted to dist/server/routes.json.module.default || module.jsx.Pages must return a full <html>...</html> document (including <head> and <body>).
global.css (optional) is included as a client entry for all routes when present. Recommended for shared styles.
Example:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- Head contents are authored directly by pages -->
</head>
<body>
<main>...</main>
</body>
</html>
Production & dev servers serve from dist/client only.
Features:
dist/client.public, max-age=31536000, immutable).public, max-age=300)..br > .gz variant if client supports & asset is compressible.../ style attempts rejected (403/404).Hashed filename detection heuristics: segment containing 8+ hex chars or an 8-char base36-ish uppercase segment before next dot.
Precedence:
CLI Flags > sxo.config.* > .env / .env.local > defaults
Command defaults:
open=trueopen=falseopen=falseExample sxo.config.json:
{
"port": 4000,
"pagesDir": "src/pages",
"outDir": "dist",
"open": false,
"build": {
"minify": false,
"sourcemap": "inline"
},
"loaders": { ".svg": "file" }
}
The build property accepts any esbuild client configuration options for the client build only. Defaults applied by SXO for the client build are: minify: true, sourcemap: isDev ? "inline" : false.
Note: The server build uses its own hardcoded defaults:
minify: true,sourcemap: false. These are not affected by thebuildproperty.
Explicit Flag Detection:
Flags only override file/env/default if explicitly passed (e.g. --open, --no-open, --open=false). Inferred / defaulted flags are filtered out (see prepareFlags()).
Key flags:
--port # Port to run the server (dev/start). Default: 3000. Example: --port 4000
--pages-dir # Path to the pages directory (default: src/pages)
--out-dir # Output directory for build artifacts (default: dist)
--open / --no-open # Auto-open the browser when the dev server is ready (toggle)
--public-path <path> # Public base URL for emitted asset URLs (default: "/"); empty string "" allowed for relative paths
--client-dir <name> # Subdirectory name for per-route client entry (default: client)
--loaders <ext=loader> # esbuild server loaders (dev/build only; repeatable or comma-separated). Example: --loaders ".svg=file" --loaders ".ts=tsx"
--verbose # Enable verbose logging for debugging and diagnostics
--no-color # Disable ANSI/colorized log output (useful for CI)
--config <file> # Load an alternate config file (e.g., sxo.config.json or .js)
Loaded (non-destructively) from .env then .env.local unless already set.
Recognized:
| Variable | Meaning | Default |
|---|---|---|
| PORT | Port | 3000 |
| PAGES_DIR | Pages directory | src/pages |
| OUTPUT_DIR | Base output directory | dist |
| OPEN | Auto-open dev browser | true (dev) |
| PUBLIC_PATH | Public base URL for asset URLs (esbuild publicPath). Empty string "" allowed and preserved. | "/" |
| CLIENT_DIR | Per-route client entry subdirectory name | client |
| LOADERS | esbuild server loaders as JSON map (e.g., {"svg":"file"} or {".ts":"tsx"}) | (unset) |
| VERBOSE | Verbose logging | false |
| NO_COLOR | Disable colorized output | (unset) |
| HEADER_TIMEOUT_MS | Node headers timeout in ms (server.headersTimeout). Set a non-negative integer to override; unset to use Node default. | (unset) |
| REQUEST_TIMEOUT_MS | Request timeout in ms (server.requestTimeout). | 120000 |
Derived / injected:
| Variable | Meaning |
|---|---|
| OUTPUT_DIR_CLIENT | <outDir>/client |
| OUTPUT_DIR_SERVER | <outDir>/server |
| SXO_RESOLVED_CONFIG | JSON blob of resolved config |
| DEV | "true" in dev command, else "false" |
| SXO_COMMAND | Current command (`dev |
| BUILD | Custom esbuild client config object propagated to child build process (only in dev/build) |
| LOADERS | esbuild server loaders map propagated to child build process (only in dev/build) |
| PUBLIC_PATH | Public base URL for assets propagated to the build (defaults to "/" when unset; empty string preserved) |
| CLIENT_DIR | Configured per-route client entry subdirectory name |
SXO includes a Rust/WASM JSX transformer that transforms JSX into template literals with small runtime helpers.
What it does:
className → class, htmlFor → for, SVG/camelCase to kebab where applicable) and supports spread props.map, flatMap, filter, reduce, slice, concat, flat, modern array copies, and forEach) with ${__jsxList(...)}
so list output is safely joined into a single string.Runtime helpers:
__jsxComponent(Component, propsArrayOrObject, children?) → renders components to string (props objects in arrays are merged).__jsxSpread(obj) → serializes element attributes from an object (boolean true becomes a valueless attribute).__jsxList(value) → joins arrays into a string; returns "" for null/undefined; passes through non-array values.SXO provides foundational security controls while giving you full control over application-specific security policies through middleware.
✅ What SXO Provides:
X-Content-Type-Options, X-Frame-Options, Referrer-PolicyescapeHtml())^[A-Za-z0-9._-]{1,200}$)❌ What You Must Implement:
See SECURITY.md for detailed guidance on:
Before deploying to production:
.env / .env.local, never committed to gitnpm audit / pnpm audit)Do not open public issues for security vulnerabilities.
Please report security issues responsibly via GitHub Security Advisories or by contacting the maintainers privately. See SECURITY.md for details.
Run all tests:
pnpm test
Focused suites:
Typical flow:
sxo build
sxo start --port 3000
Serve behind a reverse proxy (optional). Add your own middleware for:
pnpm ipnpm testfeat:, fix:, etc.).README.md / AGENTS.md) when altering behavior (manifest shape, routing semantics, middleware contract).MIT — see LICENSE.
Looking for chat? Open an issue to propose a community space if demand emerges.
226 commits
JavaScript
86.4%
Rust
10.2%
CSS
1.8%