Experimental

convex-sveltekit

SvelteKit-native Convex integration.
Real-time queries. Form spreading. SSR-to-live transport.

Before / After

Before convex-svelte + manual SSR
// +page.server.ts
export const load = async () => {
  const client = new ConvexHttpClient(url)
  return {
    tasks: await client.query(api.tasks.get, {})
  }
}

// +page.svelte
const query = useQuery(
  api.tasks.get, {},
  { initialData: data.tasks }
)
const tasks = $derived(
  query.data ?? data.tasks
)
After convex-sveltekit
// +page.ts
export const load = () => ({
  tasks: convexLoad(api.tasks.get, {})
})

// +page.svelte
const tasks = data.tasks
// Already live. That's it.

SSR'd by the load function. Auto-upgraded to real-time WebSocket via SvelteKit's transport hook.

Before Manual mutation
const client = useConvexClient()
let text = $state("")
let pending = $state(false)

async function submit() {
  pending = true
  await client.mutation(
    api.tasks.create, { text }
  )
  text = ""; pending = false
}

<!-- Wire it all up manually -->
<form onsubmit={submit}>
  <input bind:value={text} />
  <button disabled={pending}>Add</button>
</form>
After SvelteKit form DX
const form = convexForm(
  z.object({ text: z.string() }),
  api.tasks.create
)

<!-- Spread like SvelteKit forms -->
<form {...form}>
  <input {...form.fields.text.as("text")} />
  <button>Add</button>
</form>

Features

convexQuery()

Live queries that auto-update via WebSocket. Drop-in replacement for useQuery.

convexForm()

SvelteKit's form DX for Convex mutations. Spread, validate, enhance — no server hop.

convexLoad()

SSR in load functions. Transport hook auto-upgrades to live on the client.

convexCommand()

Programmatic mutations and actions matching SvelteKit's RemoteCommand pattern.

setupConvexAuth()

Better Auth integration with SSR token seeding. No unauthenticated flashes.

convexUser()

SSR-to-live user data. Seeds from JWT, upgrades to Convex subscription.

Live demo

Try it — add, toggle, delete tasks. This is a real Convex backend. Data resets every 5 minutes.

mode: server · items: 4 · live via WebSocket

How the transport works

  1. 1

    convexLoad() in your load function fetches data server-side via Convex's HTTP client.

  2. 2

    SvelteKit's transport hook serializes it across the SSR boundary.

  3. 3

    On the client, transport.decode auto-upgrades to a live WebSocket subscription with SSR data as initial state.

  4. 4

    Mutations trigger Convex to push updates to all live queries — no .refresh() needed.