Tibi

Get started

Scaffold a Tibi project, boot it via the CLI, edit a page, watch the browser reload. No main.rs needed.

1. Scaffold

$ tibi new my-app Created Tibi project at /path/to/my-app Next steps: cd my-app tibi serve # production-style boot tibi dev # boot with HMR (re-renders on .tibi edits) open http://localhost:9017

The starter template lays down:

my-app/
├── .tibi-config       # TOML: server / plugins / routes / redirects / env / store / cron
├── .gitignore
├── package.json        # `preinstall` curls the tibi installer; scripts wrap `tibi …`
└── pages/
    ├── _layout.tibi   # nav header + footer
    ├── _404.tibi
    └── index.tibi

That's it — no Cargo.toml, no src/main.rs, no Dockerfile. The tibi binary reads .tibi-config, scans pages/, and runs the server. tibi build produces a dist/ folder that drops onto every static host (Cloudflare Pages, Netlify, Vercel, GitHub Pages, S3) unchanged. The package.json is the only platform-glue we ship: its preinstall hook curls the Tibi installer when any PaaS runs npm install, so the build environment has tibi on PATH before npm run build fires — see Deploy for per-platform snippets.

2. Run

$ cd my-app $ tibi serve tibi: loaded config from /path/to/my-app/.tibi-config tibi: 2 routes from /path/to/my-app/pages / -> pages/index.tibi (404 fallback) -> pages/_404.tibi tibi-serve listening on http://0.0.0.0:9017

Need custom Rust code (extra tower layers, app state, custom handlers)? Drop a Cargo.toml + src/main.rs next to .tibi-config and call tibi::serve(tibi::TibiConfig::load(".tibi-config")?).await. The library API stays available for power users.

3. Add a page

Drop any .tibi file under pages/:

# pages/about.tibi <template> <main> <h1>About</h1> <p>Hello from /about.</p> </main> </template> <style> h1 { color: rebeccapurple; } </style>

The file-based router picks it up at /about on next request — no registration needed.

4. Dev mode + HMR

Run via the CLI to enable the file watcher:

$ tibi dev tibi dev: serving via .tibi-config at /path/to/my-app/.tibi-config with TIBI_DEV=1 tibi: 2 routes from /path/to/my-app/pages tibi-serve listening on http://0.0.0.0:9017

Edit any .tibi file — the browser receives a reload event over /__tibi/ws/hmr (WebSocket) and refreshes automatically.

5. Add a component

Drop components/Greeting.tibi at the project root (a sibling of pages/, not nested inside it):

my-app/
├── .tibi-config
├── pages/
│   └── …
└── components/
    └── Greeting.tibi
<template> <section class="hi"> <h1>Hi {name}</h1> </section> </template> <style> .hi { padding: 1rem; border: 1px solid currentColor; } </style>

Reference it from any page via <Greeting name="Alice"/>. Uppercase tag = component lookup. Each component compiles to a real Web Component (<tibi-greeting>) carrying a declarative shadow DOM, so its styles never leak and its host attributes drive its render.

6. Add interactivity

A <script> block inside the component runs once on first connect with el bound to the host element and root bound to its shadow root. Query into the component's template with root.querySelector(...). Use the global Tibi.store for IDB-backed persistence (keys follow a namespace:rest convention; declare a [[store]] entry in .tibi-config to pin the default TTL for a namespace) or Tibi.ws.subscribe(channel, fn) for server pushes (WebSocket transport).

Add a tibi attribute to the script tag (<script tibi>) to opt into the enhanced surface: $name refs from quid="name" in the template, state() / computed() / effect() reactivity, declarative template bindings ({{expr}}, :attr, t-show, t-bind, t-if / t-else-if / t-else, t-for), and onMount / onUnmount / on(...) / emit(...) lifecycle helpers. See Architecture for the full surface.

7. Compile-time data via frontmatter

Open a .tibi file with a --- fence to drop a JavaScript, Python, or TypeScript script that runs at compile time:

---js const fs = require('fs'); result = { page_count: String(fs.readdirSync(ctx.pages_dir).length), }; --- <template> <p>This site has {page_count} pages.</p> </template>

The runner exposes a ctx variable (file path, project's pages/ directory) and reads result back as JSON. Top-level keys land as {token} substitutions in the template. See Frontmatter for the full per-language contract.