Data & state
Page data
$page.data (your load data, plus url, params, route, status, form and error) can be read inside islands, and a load that returns a promise streams into the island live.
On this page
An island often needs the page’s own data, such as the current user, the route params or the result of a form action, and threading it down as props would be tedious. Instead, 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, and its data reaches every island on the page. For data an island fetches itself, on demand or after a click, use a remote function instead.
<script>
import { page } from '$app/state';
</script>
<p>Bonjour, {page.data.user.name} — you are on {page.url.pathname}</p>The whole page object crosses the boundary: data, params, url, route, status, form and error. It is the same data your load returns, treated as visible to the client, which is the same contract as on a csr = true Kit page.
Streaming promises
Kit lets load return a promise at any level of data, so the page goes out right away 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 does nothing there, but an island has a client, and 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, so the first paint never waits for the slowest promise. Each promise resolves on its own as it settles, fast ones first, and the island’s {#await} switches 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 containing more promises streams those too, recursively, as in Kit.
Custom types
Types you teach Kit through the transport hook arrive in islands as real instances, both in plain data and inside a streamed promise, so you are not limited to 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 rather than a plain copy.
Notes
- A component shared with a
csr = truepage reads the correct page object on both kinds of page. On an island page the seed feeds it, and on a Kit-hydrated page ogygia passes Kit’s own livepagethrough to it, so readingpage.datain a shared header or footer is safe everywhere without guards. - 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 with the SPA router cannot run streamed scripts in the middle of a 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 the same in both cases. - Treat load data as visible to the client, exactly as on a
csr = truepage, because it is serialized into the HTML. Don’t put anything inloaddata that you wouldn’t send to the browser. - A server island (
render: 'deferred') renders on its own endpoint, yet its components still read the page they are mounted on: ogygia looks that page up through Kit’s own data request, and the answer carries thepage.datakeys the hole’s islands read (see A lake that can refresh). - The seed is shaped to what is read. The build reads every module that imports
$app/stateor$app/storesand records the top-levelpage.datakeys it reaches, whether written aspage.data.x,$page?.data?.x,page.data['x']orconst { x } = page.data, including through aliases likeconst v = get(page)orderived(page, ($p) => …). A page then ships only the keys its islands read, plus any node an island’s props point into. A read the build cannot pin to a literal key ships everything, as before; that includes passingpage.datato a function, aliasing it whole, spreading it, indexing it with a variable, callingpage.subscribe(…)or passing on the wholepage. So on a page whose load returns a large tree for the server render, read literal keys in islands to keep the seed small. Whenever a read forces the whole tree to ship, the build names the file and line.