Problem
In a typical full-stack app, you write separate data fetching logic on the client, API endpoints on the server, cache invalidation strategies, and optimistic update handling. Every mutation requires coordinating client state, server state, and the database. This boilerplate multiplies with every new feature and is a common source of bugs, especially around stale data and race conditions.
Solution
Step 1: Install Zero and set up the schema
Zero provides automatic client-server sync with a single TypeScript schema:
npm install @rocicorp/zero
// schema.ts
import { createSchema, definePermissions } from "@rocicorp/zero";
const schema = createSchema({
version: 1,
tables: {
todos: {
columns: {
id: { type: "string" },
title: { type: "string" },
completed: { type: "boolean" },
createdAt: { type: "number" },
},
primaryKey: "id",
},
},
});
export const permissions = definePermissions(schema, () => {
return {
todos: {
row: { select: [], insert: [], update: [], delete: [] },
},
};
});
export type Schema = typeof schema;
Step 2: Query data reactively on the client
// components/TodoList.tsx
import { useQuery } from "@rocicorp/zero/react";
import type { Zero } from "@rocicorp/zero";
import type { Schema } from "../schema";
function TodoList({ zero }: { zero: Zero<Schema> }) {
const todos = useQuery(zero.query.todos.orderBy("createdAt", "desc"));
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
Step 3: Mutate data with automatic sync
// Mutations sync to server and other clients automatically
function addTodo(zero: Zero<Schema>, title: string) {
zero.mutate.todos.insert({
id: crypto.randomUUID(),
title,
completed: false,
createdAt: Date.now(),
});
// No fetch call, no cache invalidation, no optimistic update code
// Zero handles all of it
}
Why It Works
Zero maintains a client-side replica of the relevant database subset using incremental sync. When you call zero.mutate, the change applies locally for instant UI updates and syncs to the server in the background. When the server confirms or other clients push changes, the local replica updates reactively. This eliminates the entire category of data fetching, caching, and synchronization bugs. The reactive useQuery hook re-renders components automatically when underlying data changes, similar to how a spreadsheet recalculates.
Context
- Zero is built by Rocicorp, the team behind Replicache (used by Linear and Figma)
- Pairs well with AI coding agents since the sync layer removes an entire class of boilerplate the AI would otherwise need to generate
- ElectricSQL and Convex offer similar reactive sync patterns with different tradeoffs
- Convex uses cloud functions while Zero uses a client-side replica with local-first architecture
- Works with any React framework (Next.js, Vite, Remix)
- Best suited for collaborative or real-time apps where multiple clients view the same data
- Linear's sync engine (which inspired Zero) is documented at github.com/wzhudev/reverse-linear-sync-engine