liveblocks-starter
Liveblocks' Next.js Starter Kit
— Vercel's Liveblocks Starter Kit template: rich-text documents, notes, a
whiteboard and a canvas edited together in real time, with comments,
mentions, notifications, sharing and organizations — running on Cloudflare
Workers + Static Assets through
@opennextjs/cloudflare. The realtime
documents are Liveblocks rooms, in a project the
owner connects after the build; the directory the kit kept as arrays in its
source (users, organizations, memberships) and the session secret are in a
Cloudflare D1 database provisioned by Terraform through
Orun, its schema applied by
migrations, and packaged as an Orun baseline: one command takes a new
product from nothing to live on stage and prod, in its own repository, under
its own name — in ONE landing.
The kit's lib/database functions keep their signatures and semantics; they
read D1 now, as server functions. The site goes live with no Liveblocks
project: the marketing page and the demo sign-in work, and the dashboard says
how to connect one. What the baseline adds is everything needed to make a
copy of it somebody else's, built the way
cirrus-baseline is. See
specs/epics/liveblocks-starter for the
design, the milestones and the as-built status.
Instantiating a product
git clone --depth 1 --branch <baseline-tag> https://github.com/sourceplane/liveblocks-starter
cd liveblocks-starter
orun new --blueprint repo-blueprint.yaml --out ~/sites/acme-docs --run-hooks --resume \
--set reponame=acme-docs --set productname="Acme Docs" \
--set githuborg=acme-inc --set subdomain=<workers-dev-subdomain>
Or, once the platform's registry lists it, from the Orun console (Baselines →
liveblocks-starter → Build). Two phases: 01-product creates the
repository and lands the whole product as ONE pull request — intent, CI,
tooling, the D1 root, the migrations, the site and its tests — and one
convergence, per environment, applies the D1 database, then its migrations,
then deploys the site with its id; 02-docs records the live deployment
from probed facts. BOOTSTRAP.md is the operator guide;
docs/phases/ has a page per phase.
The new repository receives the product only — the app, its D1 root, its migrations and their runner, its tests, its CI, its tooling, a README of its own and its operating contract — renamed throughout to the product's name, repository, Cloudflare Worker and database names. None of this repository's machinery ships.
| What makes it a baseline | Where |
|---|---|
| The console card (inputs, secrets, programme) | blueprint.yaml |
| The build document (modules, phases, hooks) | repo-blueprint.yaml |
| This tree's identity, and the bootstrap's task contracts | .baseline/ |
| The rename engine | tooling/rebrand/rebrand.mjs |
| The bootstrap's helpers (deferrals, redeploy marker, work-tree strip) | tooling/bootstrap/ |
| The deployment-record renderer | hooks/render-deployment-docs.sh |
| The product's README and context pack | .baseline/product/README.md, ai/context/ |
| The baseline's tests: contract, wiring, leak, offline bootstrap, end to end | testing/, .github/workflows/baseline.yml |
Cutting a baseline-vN tag | .github/workflows/tag.yml |
What is here (the product)
| Surface | Where |
|---|---|
| The pages: marketing, sign-in, the dashboard, the four document kinds | apps/web/app |
| The editors, the whiteboard, the canvas, comments and the inbox | apps/web/components |
| The server actions (documents, sharing, organizations, the room auth) | apps/web/lib/actions |
| The directory on D1, the generated session secret | apps/web/lib/database, apps/web/lib/appSecret.ts |
| Liveblocks' server client, and the "not connected" state | apps/web/liveblocks.server.config.ts |
| The site's name and source link | apps/web/lib/site.ts |
| The health route the deploy smoke-tests | apps/web/app/api/health/route.ts |
| Workers build, bindings and deploy contract | apps/web/next.config.mjs, open-next.config.ts, wrangler.template.jsonc + wiring.fixture.json (→ the committed wrangler.jsonc), component.yaml |
| The D1 database (stage, prod) | infra/terraform/cloudflare-d1 |
| The schema (migrations) and the runner that applies them over the D1 REST API | packages/db, run by infra/db-migrate |
| The wrangler renderer, Liveblocks' keys to the Worker, the migration helpers | tooling/wire, tooling/migrations |
| Tests | tests/web (the directory on real SQLite with the migrations applied, the secret, the not-connected client and actions, the health route, the secrets push), tests/db (the migrations through the runner, into real SQLite) |
Getting started
pnpm install
pnpm run wire:fixture # render apps/web/wrangler.jsonc from the offline fixture
pnpm typecheck
pnpm lint
pnpm test
pnpm build # next build --webpack + opennextjs-cloudflare build → apps/web/.open-next
pnpm --filter @app/web db:migrate:local # the directory, the demo users and the secret in wrangler's local D1
pnpm --filter @app/web dev # http://localhost:3000 — the local D1 through wrangler's platform proxy
pnpm --filter @app/web preview # wrangler dev over the built Worker, same local D1
How it maps to Vercel
| Vercel-side | Here |
|---|---|
| Next.js hosting, server actions | One Cloudflare Worker (.open-next/worker.js) + Static Assets, built with webpack (Turbopack's Worker is over Workers Free's 3 MiB) |
| The kit in the liveblocks/liveblocks monorepo, npm | Its own pnpm workspace; three packages it imported without declaring (@tiptap/extension-link, @tiptap/extension-list, prosemirror-view) and @tiptap/core declared at npm's locked versions |
data/users.ts, data/organizations.ts (arrays in the source) | D1 tables users, organizations, memberships (010_directory), the kit's demo rows as 020_demo_directory; lib/database's functions read them as server functions |
NEXTAUTH_SECRET or a fallback written in auth.ts | A secret generated once per database (030_app_secrets); an AUTH_SECRET Worker secret wins |
LIVEBLOCKS_SECRET_KEY in .env.local (required — the kit threw without it) | The product's optional Orun secret, handed to the Worker as a Worker secret on deploy; without it the site serves with a "Liveblocks is not connected" dashboard |
LIVEBLOCKS_WEBHOOK_SECRET_KEY | The same, optional |
The NextAuth route on the Edge runtime (runtime = "edge", "optional") | The Node.js runtime: OpenNext serves the route there |
| Deploy on push | Orun applies the database, migrates it and deploys <repo>-web-{stage,prod} on merge to main |
| "Deploy with Vercel" (clone the template) | orun new --blueprint repo-blueprint.yaml — a new repository, branded, deployed |
How the database reaches the Worker
infra/terraform/cloudflare-d1 ──apply──► orun secret WIRING_CLOUDFLARE_D1 (stage, prod)
│ (the landing's await checks it exists)
▼
infra/db-migrate dependsOn cloudflare-d1 (enforced on main) · secretEnv WIRING_CLOUDFLARE_D1
apply: packages/db's runner → POST …/d1/database/<id>/query, each pending migration once
▼
apps/web dependsOn cloudflare-d1 + db-migrate (enforced on main) · secretEnv WIRING_CLOUDFLARE_D1_<ENV>
build: tooling/wire/live.mjs → wrangler.jsonc with the environment's database id
(dev, verify-only, renders the fixture) → next build --webpack → opennextjs-cloudflare
→ tooling/wire/worker-secrets.mjs: Liveblocks' keys, when the environment has them,
to the Worker as Worker secrets
wrangler.jsonc is committed as the fixture render of
wrangler.template.jsonc, because the deploy lane's verify-app-structure
step checks the config exists before it builds; testing/e2e.test.sh fails
if the two disagree.
CI
| Workflow | What it runs | Ships to a product |
|---|---|---|
ci.yml | the Orun pipeline, and the only workflow a product carries: orun plan --changed, then one job per lane. A pull request runs the verify lanes (packages type-checked, linted, tested and built, both test suites, the site built and dry-deployed per environment), except the bootstrap's own landings (orun/BASE-*), where plan alone is the check; a merge applies the D1 database, migrates it, then deploys stage → prod. It plans nothing while intent.yaml names the placeholder workspace ws_CHANGE_ME, as this repository does. A manual run (Orunbase's Deploy button, or Run workflow on main) plans the environment and components it names under the push-to-main trigger, so they deploy as a merge deploys them | yes |
baseline.yml | the baseline's own tiers: contract tests, the wiring path, the blueprint read by the pinned orun, the leak gate, the offline bootstrap with the product's own plan, the product built in the job tree, migrated, dry-deployed and served — with no Liveblocks project, then against a stand-in Liveblocks API | no |
tag.yml | cuts a baseline-vN tag (manual) | no |
Build from the console
Press Build: Liveblocks Starter goes into the workspace you choose (or a new one), you connect Cloudflare, and an agent builds it.
Build from the CLI
# check providers, then build
orun baseline check liveblocks-starter
orun baseline new liveblocks-starter --via-platform --repo acme/liveblocks-starter