Caution
Very Beta - This is experimental software. The API changes frequently and will break your code. Don't use this for anything production-critical. Great for prototypes, internal tools, and fun real-time multiplayer apps.
Server-driven UI for real-time web apps. No client code. No state sync. Just TypeScript + JSX.
Inspired by Phoenix LiveView | Datastar | HTMX
Tip
Built for Vibe Coding - JSX that feels like React, but there's no client bundle, no hydration, no state sync bugs. The server owns everything. Let Claude write your UI.
bunx hyperstar-cli create my-app
cd my-app
bun install
bun run devOpen http://localhost:3000 - you have a working app. Edit app.tsx and save - the browser updates instantly.
Now edit app.tsx:
import { createHyperstar, hs } from "hyperstar"
interface Store {
count: number
}
const app = createHyperstar<Store>()
const increment = app.action("increment", (ctx) => {
ctx.update((s) => ({ ...s, count: s.count + 1 }))
})
app.app({
store: { count: 0 },
view: (ctx) => (
<div id="app">
<h1>{ctx.store.count}</h1>
<button $={hs.action(increment)}>+1</button>
</div>
),
}).serve({ port: 3000 })Open multiple tabs - they all sync in real-time.
Ready to ship? Deploy to Fly.io Sprites:
bunx hyperstar-cli deploy --managedYour app is live. No Docker, no config, no CI/CD setup.
React + Next.js + TanStack Query + WebSockets + Redis + ...
1. Write React components
2. Set up API routes
3. Add TanStack Query for data fetching
4. Add WebSocket server for real-time
5. Sync client state with server state
6. Handle loading states everywhere
7. Deal with cache invalidation
8. Cry yourself to sleep
Hyperstar
1. Write a view function and actions to update state
2. Done. It's real-time. It syncs. It works.
bun add hyperstar- Server renders HTML from your
viewfunction - Clients connect via SSE (Server-Sent Events)
- When state changes, server re-renders and streams HTML diffs
- Clients morph the DOM - no full page reload
Real-time means all clients see the same state. User A clicks a button, User B sees it instantly.
Actions modify state. Changes broadcast to all clients.
// Simple action
const reset = app.action("reset", (ctx) => {
ctx.update((s) => ({ ...s, count: 0 }))
})
// Action with validated arguments
const add = app.action("add", { amount: Schema.Number }, (ctx, { amount }) => {
ctx.update((s) => ({ ...s, count: s.count + amount }))
})Use hs.* helpers with the $ prop for reactive behavior:
// Trigger action on click
<button $={hs.action(increment)}>+1</button>
// Form submission
<form $={hs.form(addTodo)}>
<input name="text" $={hs.bind(text)} />
<button type="submit">Add</button>
</form>
// Conditional visibility
<div $={hs.show(isVisible)}>Only shown when visible</div>
// Dynamic classes
<div $={hs.class("active", isActive)}>...</div>Signals are client-side state - private to each browser tab, instant updates, no server roundtrip.
interface Signals {
tab: "home" | "settings"
text: string
}
const app = createHyperstar<Store, {}, Signals>()
const { tab, text } = app.signals
app.app({
store: { ... },
signals: { tab: "home", text: "" },
view: (ctx) => (
<div id="app">
{/* Instant tab switching - no server call */}
<button hs-on:click="$tab.value = 'home'">Home</button>
<button hs-on:click="$tab.value = 'settings'">Settings</button>
{/* Show/hide based on signal */}
<div hs-show={tab.is("home")}>Home content</div>
<div hs-show={tab.is("settings")}>Settings content</div>
</div>
),
})git clone https://github.com/longtailLABS/hyperstar
cd hyperstar && bun install
bun --hot examples/simple-counter.tsx # Basic counter
bun --hot examples/todos.tsx # Full todo app
bun --hot examples/chat-room.tsx # Multi-user chat
bun --hot examples/fps-jsx.tsx # FPS stress testHyperstar apps are just Bun servers - deploy anywhere you can run bun run app.tsx. The CLI has built-in support for Fly.io Sprites, lightweight VMs that hibernate when idle.
Create a hyperstar.json in your project root:
{
"name": "my-app",
"entrypoint": "app.tsx"
}Warning
Managed hosting is for quick testing only. It may be taken down at any time without notice. Do not deploy anything critical. Use your own Sprites token for persistent deployments.
bunx hyperstar-cli deploy --managedNo account needed. Deploy instantly via longtailLABS.
For full control, use your own Fly.io Sprites token:
export SPRITE_TOKEN=your_token
bunx hyperstar-cli deploySince Hyperstar apps are standard Bun servers, deploy anywhere:
# Any VM/VPS
bun install && bun run app.tsx
# Docker
FROM oven/bun
COPY . .
RUN bun install
CMD ["bun", "run", "app.tsx"]Actions can be async for API calls, streaming, etc:
const fetchData = app.action("fetchData", async (ctx) => {
ctx.update((s) => ({ ...s, loading: true }))
const data = await fetch("/api/data").then((r) => r.json())
ctx.update((s) => ({ ...s, data, loading: false }))
})High-frequency state updates for games and animations:
app.timer("gameLoop", {
interval: 16, // ~60fps
when: (s) => s.running, // Only run when condition is true
trackFps: true, // Track actual FPS
handler: (ctx) => {
ctx.update((s) => ({ ...s, frame: s.frame + 1, fps: ctx.fps }))
},
})Simple repeating tasks:
app.interval("heartbeat", {
every: "5 seconds",
handler: (ctx) => {
ctx.update((s) => ({ ...s, lastPing: Date.now() }))
},
})Scheduled tasks:
app.cron("cleanup", {
schedule: "0 * * * *", // Every hour
handler: (ctx) => {
ctx.update((s) => ({ ...s, messages: s.messages.slice(-100) }))
},
})app.app({
store: { online: 0 },
onConnect: (ctx) => {
ctx.update((s) => ({ ...s, online: s.online + 1 }))
},
onDisconnect: (ctx) => {
ctx.update((s) => ({ ...s, online: s.online - 1 }))
},
view: (ctx) => ...
})Auto-save store to JSON:
app.app({
store: { todos: [] },
persist: "./data/todos.json",
view: (ctx) => ...
})app.app({
store: { unreadCount: 0 },
title: ({ store }) =>
store.unreadCount > 0 ? `(${store.unreadCount}) My App` : "My App",
view: (ctx) => ...
})Signals have typed methods that compose:
const { filter, count, isOpen } = app.signals
// Methods based on type
filter.is("active") // "$filter.value === 'active'"
count.gt(5) // "$count.value > 5"
isOpen.toggle() // "$isOpen.value = !$isOpen.value"
// Composition
filter.is("active").and(count.gt(0))
filter.is("all").or(isOpen)
isOpen.not()State that's private to each session but persists on the server:
interface UserStore {
theme: "light" | "dark"
}
const app = createHyperstar<Store, UserStore, Signals>()
const setTheme = app.action("setTheme", { theme: Schema.String }, (ctx, { theme }) => {
ctx.updateUserStore((u) => ({ ...u, theme }))
})
app.app({
store: { ... },
userStore: { theme: "light" },
view: (ctx) => (
<div class={ctx.userStore.theme === "dark" ? "dark" : ""}>...</div>
),
})Hyperstar is for building simple, real-time apps fast. It's not trying to replace Next.js or Rails.
Great for: Internal tools, prototypes, multiplayer games, live dashboards, chat apps.
Not built for: Multi-page SEO sites, offline-first apps, complex client-side interactions.
MIT
