xNet for React

Typed schemas, live useQuery / useMutate / useNode, and an offline-first cache. Bring a managed Hub or your own server.

pnpm add @xnetjs/react
Tasks.tsx
// Live, typed, offline-first — one hook
const { data: tasks } = useQuery(TaskSchema, {
  where: { done: false }
})

Start with the client

Define a schema, then read and write it from React. Storage, crypto, and networking are handled for you.

schema.ts
// 1. Define a typed schema (with optional authorization)
const TaskSchema = defineSchema({
  name: 'Task',
  namespace: 'xnet://my-app/',
  properties: {
    title: text({ required: true }),
    done: boolean(),
    assignee: person()
  }
})
Tasks.tsx
// 2. Read, write, and collaborate — no API, no fetch
const { data: tasks } = useQuery(TaskSchema, { where: { done: false } })
const { create, update, remove } = useMutate()

create(TaskSchema, { title: 'Ship it' })
update(TaskSchema, id, { done: true })

// Real-time collaborative documents
const { doc, peerCount } = useNode(PageSchema, id) // → TipTap / ProseMirror

More hooks: useInfiniteQuery, useComments, useHistory, useUndo. See the hooks reference →

How it works

1

Define your data

A typed schema — properties, relations, and optional authorization.

2

Call the hook

useQuery / useMutate / useNode in any component. Fully typed.

3

It syncs

Local-first cache + background sync to a Hub or your own server.

Pick your backend

The hooks are identical either way. Choose the path that fits your app.

Managed Hub

Point XNetProvider at a Hub and you're done — the same setup the xNet app uses.

App.tsx
// Point XNetProvider at a Hub URL — sync just works
<XNetProvider config={{ hubUrl: 'wss://hub.xnet.fyi' }}>
  <App />
XNetProvider>
Hub setup guide →

Bring your own server

@xnetjs/server maps your auth onto the data layer with three hooks. Reads route through the existing client seam — no hook changes.

server.ts
// your Node backend — your auth, your database
const xnet = await createXNetServer({
  trust: 'custodial',
  authenticate: (token) => verifyMySession(token),    // no DID needed
  authorizeRead: (ctx, q) => q.and({ tenant: ctx.tenant }),
  authorizeWrite: (ctx, w) =>
    ctx.tenant === (w.op === 'create'
      ? w.payload.properties.tenant
      : w.existing?.properties.tenant)
      ? { ok: true }
      : { ok: false, reason: 'wrong tenant' }
})
App.tsx
// client — the same hooks, pointed at your server
<XNetProvider config={{
  remoteNodeQueryClient: xnet.createRemoteQueryClient(getToken)
}}>
  <App />
XNetProvider>
Your-own-server guide →

See everything in DevTools

Browse your whole database, watch the change log, and profile boot — in the browser, built on the same hooks. Zero bytes in production.

Data browserChange logQuery plansLogsPerformanceSeed demo data

What you get

Offline-first

Reads and writes hit a local store instantly; sync happens in the background.

Real-time sync

Subscriptions update live as peers or the server change data.

Optimistic by default

Mutations apply locally and reconcile — no manual cache wrangling.

TypeScript-first

Full inference from your schemas through every hook.

Authorization built in

Declare roles and actions on the schema, or enforce your own server-side.

AI-assistant friendly

Three hooks and typed schemas — easy for any coding assistant to drive.

Start building

pnpm add @xnetjs/react

Not using React? xNet also works with Vue, Svelte, Swift, Rust, and more →