One platform.
Nine systems.
Visible magic.
place is a TypeScript-first web platform built on Bun. Smaller surface than Next, fewer footguns than Remix, more honest than TanStack. Nine composable systems with explicit boundaries — reactivity, component, capability, routing, data, persistence, search, security, design.
The shape
src/app.tsimport { app, discoverPages } from '@place-ts/component/server'
import { pathRouter } from '@place-ts/routing'
import { mainLayout } from './layouts/main.layout'
import { tokens } from './theme'
export default await app({
pages: await discoverPages('./src/pages'),
layout: mainLayout,
theme: tokens,
router: pathRouter,
}).start()One config. One .start() — env-aware: PLACE_BUILD=dist static-exports, anything else starts the server. The framework handles port discovery (auto-walks on EADDRINUSE), cap installation, island bundling, and pre-paint theme. No if (typeof window) branch in your code.
What you get
Routes as values
Every page is a value. Move a file, route doesn't break. No codegen, no stale .d.ts, no file-system magic. Refactors are TypeScript renames.
Capabilities, not context
Typed slots installed with explicit scope. No useContext action-at-a-distance. SSR-safe by construction — clientOnly caps auto-emit placeholders.
Reactivity, not re-render
tc39Fine-grained signals + two-color graph coloring. The same algorithm TC39 standardizes. No virtual DOM, no per-tick reconciliation.
Real SSR
streamingsuspense() with comment-marker swap. ISR via lazy stale-while-revalidate. Per-route security headers. Auto-CSRF + same-origin + body-limit defaults.
Actions, typed
Co-located on:{} dict per page. Auto-typed callers; the path is visible; no Babel pass, no encrypted action IDs. Schema-agnostic — bring your own validator.
Theme system, four-tier DX
new<ThemeToggle /> drops in one tag for defaults; useTheme() is the headless primitive for BYO UI; setTheme(name) is the escape hatch. SSR ships no theme class when undecided — OS preference drives via @media, zero blip on hard refresh.
Scaffolder that respects you
new`bunx @place-ts/create-app` — three curated templates (minimal · content · app) + five composable feature packs (theme, tests, CI, design, persistence). Interactive picker with sensible defaults; every choice exposed as a flag for CI.
Production deploy adapters
createFetchHandler() → Web-standard Request/Response. First-class adapters for Cloudflare Workers, Vercel Build Output, Deno Deploy. Static export emits a CSP-ready _headers file.
Server logs that scan
newPLACE_LOG_LEVEL env var · scoped [hmr] / [isr] prefixes · terminal-rendered error frames with source-mapped file:line · static-asset noise suppressed at default level. Compact startup banner with Local + Network URLs.
Strict-CSP by default
No inline scripts, no inline styles (style:* directives use setProperty). Content-Security-Policy ships sane defaults; CSRF, same-origin, body-limit, prototype-pollution guards all on.
Motion as state
@place-ts/reactivity/motion — animate() returns a Derived<number>. Springs, tweens, sequences. SSR resolves to rest. No <motion.div> factory, no two-runtime split, no 34KB floor.
One CLI, zero config
bunBun.serve + Bun.build out of the box. Tailwind v4 inline. Auto port-walk on EADDRINUSE. Content-hashed prod bundles. Source-map-aware dev overlay.
Nine systems, named
Each system has its own charter, its own ADRs, and its own deliberately not doing list. Use what helps. Drop what doesn't.
Built on the platform itself.
This docs site is a place app. The interactive reactivity demo on the reactivity page uses the same @place-ts/reactivity primitives the framework ships. The Cmd+K search palette uses the same globalKey + state you'd use in your app. There's no privileged internal surface you can't reach.