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. 2. A native framework called PRISM — TSX → string-builder server rendering, no React on the server, with a build-time compile pass that lands SSR at ~14 µs per page on a release V8 isolate. 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 for ~15K req/s |
| 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 beats Marko 5 by
~5× and Solid 1.9 by ~7× on the same fixture).
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 |
`` + 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.
Two production PRISM sites today: sites/prism-demo and sites/status.
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-stack/nextjs |
TypeScript | Direct imports |
| Hono | @bext-stack/hono |
TypeScript | Middleware + standalone client |
| Express | @bext-stack/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:
1. Next.js — next.config.{js,ts,mjs} present
2. Hono — hono in package.json dependencies
3. Express/Fastify — .listen() pattern in entry point
4. FetchHandler — export default { fetch } pattern (Bun/Deno/Workers)
5. bext Native — bext.config.toml present
6. Static Site — only public/ or static/ 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 optional TTL:
await kv.set("session:abc", userData, { ttl: 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 | Latency | Throughput |
|---|---|---|
| Classic (per-request boot) | 1–5ms | ~1K req/s |
| Worker (pre-loaded kernel) | 34us | ~15K req/s |
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.