Frameworks on bext
bext is two things at once:
- A TLS-terminating Rust HTTP frontend that masquerades as nginx for compatibility, then routes per-host through whatever framework the site declares.
- A native framework called PRISM — TSX → string-builder server rendering, no React on the server, with a build-time compile pass that averages 15.7 µs on the current byte-validated 38.3 KB fixture, versus 75.3 µs through automatic JSX. See PRISM and the compile pass.
You can run a site on PRISM, or you can run a site on Next.js / Hono /
Express / Laravel / Symfony / a fetch-handler app and use bext's
adapters to call its platform services. The choice is per-site, in
each bext.config.toml.
When to pick what
| If you're building... | Pick |
|---|---|
| Public marketing / docs / blog / status page | PRISM — zero-JS pages, microsecond renders, default-on compile pass |
| CMS-driven content site with islands of interactivity | PRISM + islands |
| Data-driven app with form submissions | PRISM + server actions / loaders |
| Existing Next.js app | Next.js — bext serves it via [framework] type = "nextjs" |
| Existing Laravel/Symfony/PHP app | PHP adapter — worker mode keeps the kernel warm between requests |
| New app with team familiar with React | Next.js if you need RSC, PRISM if you don't |
PRISM is the recommended choice for new bext-only projects. It has
the lowest config overhead (zero-config default for any directory
with [framework] type = "prism" and src/app/page.tsx) and the
best per-render performance (the compile pass renders well ahead of
Marko 5 and Solid 1.9 on the same fixture — see the
compile pass benchmarks).
PRISM (native)
bext's own framework. JSX compiles to direct HTML strings via the
h() runtime; a build-time compile pass folds whole component
trees into single string concatenations. No React on the server,
no virtual DOM, no diffing. Optional islands ("use client") for
interactivity.
| Topic | Doc |
|---|---|
| Framework overview, file conventions, server actions | PRISM |
h() runtime, attributes, escaping rules |
JSX runtime |
| Build-time compile pass — what folds, what doesn't, perf data | Compile pass |
Remix-style loader / action exports |
PRISM data |
<Form> + multipart + @bext:max-body directive |
Server actions |
| React-hydrated interactive components | Islands |
| Fine-grained reactivity, no React, ~3 KB hydrate | Signals |
| Tailwind v4 integration | Tailwind |
PRISM ships two hydration models: "use client" (React-based
islands) and "use signals" (bext's signals runtime — fine-grained
reactivity, no virtual DOM, ~3 KB hydrate runtime). Both can coexist
on the same page; the loader script picks the right bundle by
data-runtime attribute.
PRISM is used across first-party production sites including bext.dev,
docs.bext.dev, status.bext.dev, and blog.bext.dev; sites/prism-demo
remains the executable feature fixture.
Framework adapters (Next.js, Hono, Laravel, etc.)
For sites built on a different framework, bext provides SDK adapters that expose its platform services — cache invalidation, real-time pub/sub, KV store, durable queues, scheduled tasks — through an idiomatic API.
| Framework | Package | Language | Pattern |
|---|---|---|---|
| Next.js | @bext/nextjs |
TypeScript | Direct imports |
| Hono | @bext/hono |
TypeScript | Middleware + standalone client |
| Express | @bext/express |
TypeScript | Middleware + standalone client |
| Laravel | bext/laravel |
PHP | Service provider + helpers |
| Symfony | bext/symfony |
PHP | Bundle + autowiring |
| PHP Micro | bext/framework |
PHP | File-based routing |
Auto-Detection
bext automatically detects your framework with zero configuration:
- Next.js —
next.config.{js,ts,mjs}present - Hono —
honoinpackage.jsondependencies - Express/Fastify —
.listen()pattern in entry point - FetchHandler —
export default { fetch }pattern (Bun/Deno/Workers) - bext Native —
bext.config.tomlpresent - Static Site — only
public/orstatic/directories
The detected framework determines which source transforms are applied:
- Next.js gets 14 transforms (import stripping, barrel optimization, font optimization, server actions, CSS modules, etc.)
- Hono gets 3 transforms (env inline, import strip, alias rewrite)
- Generic frameworks get 3 transforms (env inline, alias rewrite, process polyfill)
- Static sites get no transforms
Architecture
All SDK adapters communicate with bext via HTTP calls to a local sidecar:
┌──────────────┐ HTTP (localhost:3061) ┌──────────────┐
│ Your App │ ──────────────────────────→ │ bext Server │
│ (Next.js, │ ← cache, realtime, kv, │ │
│ Laravel, │ queue, tasks │ ISR Cache │
│ etc.) │ │ PubSub Hub │
└──────────────┘ │ KV Store │
│ Queue │
│ Scheduler │
└──────────────┘
Environment variables:
BEXT_SDK_URL— sidecar base URL (default:http://127.0.0.1:3061)BEXT_APP_ID— application identifier for multi-app isolation (default:default)
Common Services
Every adapter provides access to the same five services:
Cache Invalidation
Invalidate ISR-cached pages by tag or path:
// TypeScript
await cache.invalidateTag("products");
await cache.invalidatePath("/api/products");
// PHP
bext_invalidate("products");
app('bext.cache')->invalidatePath("/api/products");
Real-Time Pub/Sub
Push events to connected browsers via SSE/WebSocket:
await realtime.publish("orders", { id: 123, status: "shipped" });
bext_publish("orders", ["id" => 123, "status" => "shipped"]);
KV Store
Key-value storage with an optional TTL (seconds) as the third argument:
await kv.set("session:abc", userData, 3600);
const data = await kv.get("session:abc");
await kv.delete("session:abc");
Durable Queues
Background job processing with retry support:
await queue.push("emails", { to: "user@example.com", template: "welcome" });
const msg = await queue.pull("emails");
await queue.ack("emails", msg.id);
Scheduled Tasks
Cron-style task registration:
await tasks.register("cleanup", { cron: "0 2 * * *", command: "cache::gc" });
const all = await tasks.list();
await tasks.cancel("cleanup");
PHP Worker Mode
The PHP adapters (Laravel, Symfony) support worker mode for dramatically improved performance:
| Mode | Per-request overhead |
|---|---|
| Classic (per-request boot) | 1–5 ms |
| Worker (pre-loaded kernel) | tens of microseconds |
Worker mode keeps the framework kernel loaded in memory and processes requests in a loop, eliminating per-request bootstrap overhead. See the Laravel and Symfony pages for setup details.
Rendering Modes
Each app in your bext.config.toml can use a different rendering strategy:
[[apps]]
hostname = "blog.example.com"
root = "./blog"
rendering = "isr" # Cached with background revalidation
revalidate = 3600
[[apps]]
hostname = "api.example.com"
root = "./api"
rendering = "ssr" # Fresh render every request
[[apps]]
hostname = "docs.example.com"
root = "./docs"
rendering = "static" # Pre-built at deploy time
Available modes: isr (default), ssr, static, swr.
The rendering mode is set per-app in bext.config.toml — every app in your fleet can use a different strategy. See the deployment modes guide for a detailed comparison.
Related
- PRISM framework — bext's native TSX framework, zero-JS by default
- Compile pass — how PRISM folds component trees at build time
- Islands — adding client-side interactivity to PRISM pages
- Next.js on-demand rendering — serve a Next.js app without a build step
- Caching — TTL, stale-while-revalidate, and stampede guard
- Deployment modes — ISR vs SSR vs static vs SWR compared