Data & state
Page data
$page.data — your load data, plus url / params / route / status / form / error — reads inside islands, and a load that returns a promise streams into the island live.
On this page
An island reads $page.data — and the rest of Kit’s page object — exactly as a normal Kit component does. Your load runs on the server as always; its data reaches every island on the page.
<script>
import { page } from '$app/state';
</script>
<p>Bonjour, {page.data.user.name} — you are on {page.url.pathname}</p>Everything on the page object crosses the boundary: data, params, url, route, status, form, and error. It is the same data your load returns, treated as client-visible — the same contract as a csr = true Kit page.
When to use
Reach for this whenever an island needs the page’s own data — the current user, the route params, a form action result — without you threading it down as a prop. For data an island fetches itself (on demand, on a click), use a remote function instead.
Streaming promises
Kit lets load return a promise at any level of data — you send the page now and the value follows when it resolves. On a normal Kit page the browser streams it in. A csr = false page has no page-level hydration, so Kit’s own stream is inert there — but an island has a client, so ogygia streams the promise into the island.
export const load = () => ({
country: 'FR', // sent immediately
slow: fetch('/api/recommendations').then((r) => r.json()) // a promise — streamed
});<script>
import { page } from '$app/state';
</script>
{#await page.data.slow}
<p>Loading recommendations…</p>
{:then recs}
<ul>{#each recs as r}<li>{r.title}</li>{/each}</ul>
{/await}The page shell — and the island’s Loading… branch — paint immediately; first paint is never blocked waiting on the slowest promise. Each promise resolves on its own as it settles (fast ones first), and the island’s {#await} flips to its result live, with no extra request.
A promise that rejects streams as an error and shows the {:catch} branch. A promise that resolves to a value holding more promises streams those too, recursively — the same as Kit.
Custom types
Types you teach Kit through the transport hook round-trip into islands as real instances — both in plain data and inside a streamed promise — not just built-in types like Date and Map.
import * as ogygia from 'ogygia';
export class Money {
constructor(public cents: number) {}
get pretty() { return `$${(this.cents / 100).toFixed(2)}`; }
}
export const transport = {
...ogygia.transport,
Money: { encode: (v) => v instanceof Money && [v.cents], decode: ([c]) => new Money(c) }
};page.data.price.pretty then works inside the island — the getter runs because decode rebuilt the class, not a plain copy.
Notes
- A
formaction result (page.form) andpage.errorreach islands the same way, so an island can render the outcome of a<form method="POST">without a prop. - Client navigation (the SPA router) can’t run streamed scripts mid-swap, so on those loads a promise is awaited on the server and handed to the island already settled —
page.data.xis still a promise you{#await}, so your island code is identical either way. - Treat load data as client-visible, exactly as on a
csr = truepage: it is serialized into the HTML. Don’t put secrets inloaddata you wouldn’t ship to the browser. - A server island (
render: 'deferred') renders in isolation on its own endpoint and does not receive the page seed; give it what it needs through its props or a remote function.