Elizabeth is a Bun full-stack web framework with .liz components, server rendering, file routes, API routes, scoped styles, CSS modules, client islands, and automatic layout transition.
This is an early 0.0.x release. Expect rough edges while the syntax and runtime settle.
bun create elizabeth my-app
cd my-app
bun run devThe dev server starts on port 3712 by default.
@default
<HomePage>
const items = [
{ text: "Fast" },
{ text: "Server first" },
{ text: "Scoped styles" },
];
<style>
.hero {
padding: 32px;
border: 1px solid rgba(0, 0, 0, .12);
border-radius: 8px;
}
</style>
<main className="hero">
<h1>Elizabeth</h1>
<ul>
{
for (const item of items) {
<li>{item.text}</li>
}
}
</ul>
</main>
</HomePage>{...} inside markup is JavaScript. To render text that looks like template syntax, use a string expression.
Pages live in src/pages by default:
src/pages/index.liz -> /
src/pages/about.liz -> /about
src/pages/users/[id].liz -> /users/:id
src/pages/404.liz -> custom 404
API routes live in src/api by default:
export function GET() {
return Response.json({ message: "Hello from Elizabeth" });
}.liz endpoint files can return rendered HTML fragments:
<POST>
const form = await ctx.request.formData();
<p>{form.get("title")}</p>
</POST>
Route roots can be configured:
export default {
pageRoutes: {
"src/pages": "/",
},
apiRoutes: {
"src/api": "/api",
},
};layout.liz wraps child routes:
@default
<RootLayout>
<html>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
{children}
</body>
</html>
</RootLayout>When a shared layout exists, Elizabeth enhances same-origin link clicks by fetching the next page and swapping only the layout child boundary.
Use @client for browser-interactive components:
import { clientState } from "elizabeth/client"
@client
@public
<Counter>
const [count, setCount] = clientState(0);
<button onClick={() => setCount(count + 1)}>{count}</button>
</Counter>
bun run build
bun run startbun run build creates both static HTML for static routes and a production dist/server.js.