Skip to content

Data & state

Data & remote functions

All six Kit remote primitives operate inside islands: query, command, form, and more.

On this page

Islands are real Svelte components (see the last chapter). Thus the remote functions of Kit operate inside them, without changes. All six primitives (query, query.batch, query.live, command, form, and prerender) run in an island exactly as they do in a Kit page.

When to use

Remote functions are how an island speaks to the server: read data (query), change it (command / form), or stream it (query.live). Use them when an island needs server data, or must keep a change. They replace the fetch-and-endpoint boilerplate that you would write, and the server validates each call.

<script>
  import { greeting } from './hello.remote';
  const who = await greeting();
</script>

<p>{who}</p>

SSR query results are seeded into the client cache. Thus an island that reads a query during the server render does not fetch again on hydrate, in production. Dev degrades to a refetch. See the note below.

The seed is sized to its readers. A query result is seeded only when some island on the page can call that remote (its built chunks import the module). A lake, a server island, or the page’s own script that awaits a query for the server render alone ships HTML, not a seed. So a whole CMS entry rendered through a lake costs the page nothing twice.

Try it

This counter is a client island. It reads a query and mutates through two commands. The value lives on the server. Each button is a real round-trip. A .refresh() then reads the value again. It is shared state: open this page in a second tab, and both tabs move.

server count —

Every click is a real round-trip. Open this page in two tabs — the count is shared.

let count = 3;
export const getCount = query(async () => count);
export const bump = command(async () => (count += 1));

Forms and commands

form() and command() POST to their endpoints, and operate from inside an island. On success, form() runs invalidateAll from Kit. ogygia treats that as a soft seed refresh: it fetches the current page again, merges <head>, and refreshes the in-place data seeds. There is no view transition, no body swap, no island remount, and no clear of live queries.

<script>
  import { addEntry } from './guestbook.remote';
</script>

<form {...addEntry}>
  <input name="msg" />
  <button>Sign</button>
</form>

If an island needs a query to update after a mutation, call .refresh() on it. Or pair submit().updates(query) with server-side requested(query).refreshAll(). updates alone does not fill the response.

Mutation guards

A write to a captured host variable from island markup is a mistake. The value is a serialized snapshot, not live state. In dev, ogygia wraps each snapshot in a deep proxy. The proxy does a console.warn, with the path of the prop, when you mutate one. The write still applies. It is only a warning. Treat it as the signal to move that state into the island.

Dev is not prod

In dev, SSR query seeding degrades to a refetch, and the mutation proxy runs only in dev. Check data flows against a production build.

Do / don’t

  • Do call .refresh() (or submit().updates(q) plus server requested(q).refreshAll()) when a mutation must update a query. updates alone does not fill the response.
  • Do test data flows against a production build. Dev seeding and the mutation guard behave differently.
  • Don’t mutate a captured host prop from island markup. It is a serialized snapshot. Move mutable state inside the island.

Next chapter: collections turn your content into typed entries.