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:

  1. Read bext.config.toml and saw [framework] type = "prism". That tells the request dispatcher to route through the PRISM pipeline instead of the generic / Next.js / proxy paths. 2. Walked src/app/ and discovered routes — page.tsx is /, about/page.tsx would 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