Build a synced app

Scaffold with create-rindle

Generate a SQL-first Rindle app on TanStack Start: browser client, API authority, rindled daemon, migrations, generated schema, SSR, and devtools in one template.

View as Markdown

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_TOKEN on 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, and api.rindle.mutate.tsx are 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 let pnpm dev regenerate 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.ts and the policy in server/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