Quickstart
This guide takes you from zero to a running bext site in under 5 minutes using PRISM, bext's native framework. PRISM is the fastest path to a working bext site: zero config, no build step required during development, and the build-time compile pass kicks in automatically.
If you have an existing Next.js / Hono / Laravel app you want to serve via bext instead, jump to Frameworks → Next.js or pick the matching adapter from the framework overview.
Install bext
The quickest path is the prebuilt release binary:
curl -fsSL https://bext.dev/install | sh
That puts bext in ~/.local/bin (or /usr/local/bin if you have
sudo). Alternatively, see the installation guide
for cargo, Docker, and per-platform instructions.
Verify:
bext --version
# bext 0.x.y
Create a PRISM site by hand
PRISM is intentionally tiny — three files get you a running site.
mkdir my-site && cd my-site
mkdir -p src/app
src/app/page.tsx:
export default function HomePage(props: {
searchParams?: { name?: string };
}) {
const name = props.searchParams?.name ?? "world";
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<title>my-site</title>
</head>
<body>
<h1>Hello, {name}!</h1>
<p>Try /?name=you to change the greeting.</p>
</body>
</html>
);
}
bext.config.toml:
[server]
port = 3088
app_dir = "."
[framework]
type = "prism"
tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@bext-stack/framework",
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true
}
}
package.json:
{
"name": "my-site",
"type": "module",
"dependencies": {
"@bext-stack/framework": "*"
}
}
Install and run:
bun install
bext run .
Open http://localhost:3088 — you should see "Hello, world!"
What just happened
When you ran bext run ., bext-server:
- Read
bext.config.tomland saw[framework] type = "prism". That tells the request dispatcher to route through the PRISM pipeline instead of the generic / Next.js / proxy paths. 2. Walkedsrc/app/and discovered routes —page.tsxis/,about/page.tsxwould be/about, etc. 3. First request triggered an on-demand compile of the matched route via bext-turbopack. The compile pass folded the static parts of your JSX into HTML strings (see the compile pass). 4. Subsequent requests hit a warm V8 isolate with a cached page context — typical render time is 1-5 ms. On a 50-row table page, the compile pass brings that down further to 77 µs (release mode).
No bun build, no vite dev, no separate dev/prod toolchain. The
same bext run works in development and production.
Add a route
Create src/app/about/page.tsx:
export default function AboutPage() {
return (
<main>
<h1>About</h1>
<p>Built with bext + PRISM.</p>
<p><a href="/">← home</a></p>
</main>
);
}
Hit http://localhost:3088/about. No restart needed — bext's file watcher invalidates the route's compile cache on save.
Add a layout
Create src/app/layout.tsx:
export default function RootLayout(props: { children: any }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<title>my-site</title>
</head>
<body>
<nav>
<a href="/">home</a> · <a href="/about">about</a>
</nav>
{props.children}
</body>
</html>
);
}
Then strip the <html> / <head> / <body> from page.tsx and
about/page.tsx — the layout wraps them automatically.
The dispatcher composes layouts inside-out:
RootLayout(NestedLayout(Page(props))). Each layout.tsx in the
directory chain wraps the deeper content via its children prop.
Add an island for interactivity
PRISM pages are pure server-rendered HTML by default. For
interactive bits, add a "use client" component under src/islands/:
src/islands/Counter.tsx:
"use client";
export default function Counter({ start = 0 }: { start?: number }) {
const [n, setN] = useState(start);
return <button onClick={() => setN(n + 1)}>Count: {n}</button>;
}
Use it from a server page:
export default function Page() {
return (
<main>
<h1>Click below</h1>
</main>
);
}
The server renders a <bext-island> placeholder; the client
loader fetches /islands/Counter.js, parses props from the
data-props attribute, and mounts. See
Islands for the full story.
Or with bext signals (no React, ~3 KB hydrate)
Same component, different runtime — no React in the bundle, no
re-render on update. The build pipeline auto-detects "use signals"
and emits a separate per-island bundle that uses bext's signals
runtime instead of React:
"use signals";
/** @jsxImportSource @bext-stack/framework/signals */
export default function Counter({ start = 0 }: { start?: number }) {
const n = signal(start);
return <button onClick={() => { n.value++; }}>Count: {n.value}</button>;
}
Mount via signalsIsland("Counter", Counter, { start: 42 }). See
Signals for primitives, the auto-wrap
compile pass, and `` for reactive arrays.
Deploy
bext is a single binary. Most production deploys are:
# On the server
bext run /var/www/my-site
…behind a TLS terminator (or use bext's built-in TLS — see Configuration → TLS).
For multi-site hosts, switch to the masquerade mode and let bext route by hostname:
bext --nginx-masquerade
See Deploying for systemd units, Docker, and Cloudflare Tunnel patterns.
Next steps
| Topic | Doc |
|---|---|
| The full PRISM framework reference | PRISM |
| The build-time compile pass | Compile pass |
h() runtime details + escape rules |
JSX runtime |
Server-side data with loader + action |
PRISM data |
| Islands (interactive components) | Islands |
| Tailwind v4 integration | Tailwind |
| Routing details | Routing |
| Deploy patterns | Deploying |