Fetch data at build time, per request, or on the client.
#Data Fetching
Krate offers complementary data-fetching models: build-time and serve-time data via component tiers, route params via generateStaticParams, and client-side resources.
#Build-time data (server components)
Mark a component with // @server (or a *.server.tsx file). It is evaluated at build time and its HTML is baked into the page with zero client JavaScript:
// @server
export default function ServerTime() {
return <time>{new Date().toUTCString()}</time>;
}Because server components run at build time, they can fetch external data and render the result — ideal for data that doesn't change per request.
#Per-request data (runtime components)
Mark a component with // @runtime (or a *.runtime.tsx file). It is rendered at request time by the SSR sidecar (Node/bun/deno) and spliced into the static shell as its own region — streamed to the client without a full-page re-render:
// @runtime
export default function PriceTag({ price }) {
return <span>{formatCurrency(price)}</span>;
}Runtime components cover the "changes per request" case that previously used page-level server-side data functions. A page that imports them is treated as streaming: its shell is baked (layout, server components, suspense fallbacks) and each runtime component resolves independently.
#ISR / SSR page data
For whole pages whose body is request-time (e.g. a dynamic route without known params), opt the page into ISR or SSR and read params/query from the page props:
// src/pages/video/[id].tsx
export const config = { isr: true, revalidate: 60 };
export default function VideoPage({ params }) {
return <h1>Video {params.id}</h1>;
}- ISR caches the rendered page body per URL variant and revalidates it in the background after
revalidateseconds. - SSR renders the page body on every request with the live params/query.
Both bake everything else (layout, head, static content) into a shell at build time; only the page body is dynamic.
#Dynamic route params (generateStaticParams)
For statically-built dynamic routes, generateStaticParams pre-generates the concrete URLs, and each param value is passed to the page component as params:
// src/pages/video/[id].tsx
export default function VideoPage({ params }) {
return <h1>Video {params.id}</h1>;
}
export function generateStaticParams() {
return [
{ id: "abc-123" },
{ id: "def-456" },
];
}Each { id: ... } set is expanded into a static page (/video/abc-123, /video/def-456) whose component renders with params.id set to the matching value. Directly destructured params ({ id }) work the same way.
#Client-side data (createResource)
import { createResource } from '@krate/runtime';
const [data, actions] = createResource(source, fetcher);- Reactive — auto-refetches when the source signal changes.
- Abort — in-flight requests are aborted when the source changes.
- Optimistic updates —
mutate(prev => …). - Manual refetch —
refetch().
export default function User({ id }) {
const [user, { refetch }] = createResource(
() => id,
async (id) => (await fetch(`/api/user/${id}`)).json(),
);
return (
<div>
{user.loading && <p>Loading…</p>}
{user() && <p>Hello, {user().name}</p>}
<button onClick={refetch}>Refresh</button>
</div>
);
}#Other request-time concerns
Per-request redirects and headers are handled by middleware.ts, which runs before page rendering. For your own JSON endpoints, see API Routes.
Last updated: 2026-10-16
Contributors: Noinkin