Loading Data
A load function fetches data for a page before it is rendered. Return data with a response helper like ok, and it becomes $props.data in the page component.
Server loads
Put load in +page.server.ts to run it on the server, on every request. This is where you talk to your database:
// routes/posts/+page.server.ts
import { ok } from "@torpor/build";
import { type PageServerEndPoint } from "@torpor/build";
import { getPosts } from "@/lib/db";
export default {
load: async () => {
const posts = await getPosts();
return ok({ posts });
},
} satisfies PageServerEndPoint; // views/Posts.torp
export default function Posts($props: { data: { posts: Post[] } }) {
@render {
@for (let post of $props.data.posts) {
<article>{post.title}</article>
}
}
} A server load runs during the first render, and its data is embedded in the HTML, so the browser doesn't fetch it again when it hydrates. A prerendered page with a server load can therefore hydrate with its data on a static host, with no server at all.
Client loads
Put load in +page.ts to run it in the browser during client-side navigation, for example fetching from an external API:
// routes/weather/+page.ts
import { ok } from "@torpor/build";
import component from "@/views/Weather.torp";
import { type PageEndPoint } from "@torpor/build";
export default {
component,
load: async () => {
const response = await fetch("https://api.example.com/weather");
return ok({ forecast: await response.json() });
},
} satisfies PageEndPoint; Layouts load too
A _layout.server.ts load runs before the page's load, and its data accumulates top down: each load receives the data loaded by the layouts above it, may add to it, and the merged result is passed into the page as $props.data.
The load event
Server loads receive an event with url, params, data (from the layouts above), appData (set by hooks), request, cookies and headers. Client loads receive url, params and data.
Query strings
Declare a load schema to validate the URL's query string. The values are validated and typed before load is called, and an invalid query string gets a 422 response without running your code:
const schema = {
load: z.object({ page: z.coerce.number() }),
};
export default {
schema,
load: async ({ query }) => {
const { page } = await query();
return ok({ page });
},
} satisfies PageServerEndPoint<"/posts", Record<string, any>, typeof schema>; Caching
Server load data is embedded in the rendered HTML so the browser can hydrate without a second request. If that data is specific to the signed-in user, a shared cache (a CDN, or a proxy) must not store the page — otherwise it can serve one user's data to the next. Set Cache-Control on the response from a load or hook:
// routes/account/+page.server.ts
import { ok } from "@torpor/build";
import { type PageServerEndPoint } from "@torpor/build";
export default {
load: async ({ headers }) => {
headers.set("Cache-Control", "private, no-cache");
return ok(await getAccount());
},
} satisfies PageServerEndPoint; Use private (or no-store for anything more sensitive) on user-specific pages, and public with a max-age on pages that are the same for everyone. In dev, a page that embeds data without setting Cache-Control logs a one-time reminder, and setting it — any value, including public — silences it.
Redirects and errors
Loads may return any response instead of data: use seeOther("/login") to redirect, or notFound() to show the error page.