The synced-app quickstart builds a Rindle app by
hand so you can see every seam. create-rindle is the shortcut after that: it
generates the same three-tier shape as a small, running TanStack Start
app.
Use it when you want a project to edit instead of copy-pasting the quickstart. The template is intentionally small - a rooms-and-messages app with live message counts, optimistic writes, a rejection path, SSR, generated schema, and dev-only Rindle devtools.
Create an app
npm create rindle@latest my-app
# or
pnpm create rindle my-app
# or
npx create-rindle my-app
Then run it:
cd my-app
pnpm dev
pnpm dev uses concurrently to run
two processes — the standard TanStack Start dev shape: a Rindle process
(rindle up rendering the committed rindle.ncl and supervising the write-master +
follower pair, applying migrations to the master and regenerating shared/schema.gen.ts
from the follower on every migrations/*.sql change) and a web process (vite dev
on port 3000, serving the app and the /api/rindle/* server routes). It sets
RINDLE_REPLICATOR_URL so the API authority’s write leg reaches the master. Run either
alone with pnpm fleet / pnpm dev:web.
Open two browser windows, create a room, and the room count updates live in both as
you post messages. Try a room name or message containing "spam" to watch an
optimistic write snap back after the API authority rejects it.
The local pair binds to loopback and runs without auth tokens; a real deployment sets them (tokens on the write-master and follower plus a verified
RINDLE_DAEMON_TOKEN/RINDLE_REPLICATOR_TOKENon the server), which never reach the browser.
The generated app requires Node >= 22.18.
What it generates
It is the same architecture as the quickstart, packaged as a starter:
| Tier | In the template | What it does |
|---|---|---|
| Browser | src/rindle-client.ts, src/components/*.queries.ts, src/routes/* |
Runs createRindleClient, local wasm IVM, optimistic writes, useRoot/useFragment, and co-located queries/fragments |
| API authority | server/app-api.ts, server/rindle-http.ts, src/routes/api.rindle.*.tsx |
Resolves named queries, runs authoritative SQL mutators (a SplitDaemonClient: writes → the write-master, reads → the follower), and exposes the Rindle API through TanStack Start server routes |
| Data tier | rindle.ncl, migrations/*.sql |
rindle up renders rindle.ncl and supervises the write-master + follower pair, applies migrations to the master, generates schema from the follower, and streams live deltas |
The schema is SQL-first. Edit or add migrations/*.sql; the dev loop applies the
migration to the write-master and regenerates shared/schema.gen.ts from the follower’s
introspected schema. Keep relationships, normalization, and mutators in
shared/app-def.ts.
Why TanStack Start
The starter uses TanStack Start because it gives the template one coherent home for the browser, SSR, and server routes:
src/routes/api.rindle.query.tsx,api.rindle.read.tsx, andapi.rindle.mutate.tsxare the browser-facing API routes.- SSR reads call the same app authority in-process, so first paint and client subscriptions use the same query registry.
- TanStack Router’s file routes keep app screens and Rindle query modules close without forcing the Rindle APIs themselves to depend on TanStack.
Rindle is not TanStack-specific. The important parts are the three tiers and the contracts: SQL migrations, generated schema, named queries, predicted mutators in the browser, authoritative mutators on the server, and the write-master + follower as the live data tier. TanStack Start is just the starter’s application shell.
Where to look first
| File | Why it matters |
|---|---|
migrations/0001_init.sql |
The source-of-truth SQL schema |
shared/schema.gen.ts |
Generated @rindle/client schema; do not hand-edit |
shared/app-def.ts |
Schema re-export, relationships, normalization, and predicted mutators |
src/components/*.queries.ts |
Named root queries and fragments, co-located with UI |
src/rindle-client.ts |
The one browser client setup call |
src/ssr.ts |
Server-only first-paint preload helper |
src/devtools.tsx |
Dev-only Rindle devtools panel mount |
server/app-api.ts |
Authoritative query registry, SQL mutators, policy |
rindle.ncl |
The topology rindle up (local) and rindle deploy (cloud) both read — followers = 1, the colocated pair; loopback, no auth token in dev |
AGENTS.md |
The rules of the app for coding agents (never edit schema.gen.ts, deterministic mutators, named queries, …) — Claude Code, Cursor, and Codex read it automatically |
Customize it
Treat the generated app as a working baseline:
- change tables in
migrations/*.sql, then letpnpm devregenerate the typed schema; - add relationships and mutators in
shared/app-def.ts; - add named root queries or fragments beside the components that read them;
- replace the demo auth seam in
shared/auth.tsand the policy inserver/app-api.ts; - keep the daemon token server-only if you point the app at Rindle Cloud or a self-hosted daemon.
The same rindle.ncl drives the cloud: rindle login, then pnpm rindle:deploy
provisions (or re-attaches) the managed write-master + follower and records the binding
in .rindle/cloud.json (commit it), and pnpm rindle:migrate:remote pushes
migrations/*.sql to the write-master. Local dev reads the same file — rindle up
renders it to the colocated pair. See deploy.
For the underlying commands, see @rindle/cli. For the
manual version of the same app shape, keep the quickstart
open beside the generated project.
Next steps
- Synced-app quickstart - the same architecture, built by hand.
- The three-tier architecture - why the browser, API authority, and daemon are separated.
@rindle/cli- therindletoolchain the template uses for the daemon, migrations, and schema generation.- The browser client -
createRindleClient, optimistic mutators, and live reads. - Server rendering - the first-paint preload and live-store handoff.
- Devtools - the dev-only mutation timeline, query inspector, and delta stream.