place

Data fetching

Three patterns. load() for server-rendered data. revalidate for cached data with background refresh. resource() for client-only fetches with reactive status.

load() — server-rendered

ts
// Server-only loader. Result is serialized into the SSR'd HTML // and read back on the client at boot. page('/posts/:id', { load: async ({ params }) => ({ post: await db.posts.findOne(params.id), }), view: ({ post }) => <Article post={post} />, })
load runs server-side; the return type is serialized to JSON and read back at client boot. Don't return classes or functions — they won't survive the round-trip.

ISR — lazy stale-while-revalidate

ts
// Lazy stale-while-revalidate. The first request after `maxAge` // returns the stale value and triggers a background regeneration. page('/blog/:slug', { load: async ({ params }) => ({ post: await fetchPost(params.slug) }), revalidate: { maxAge: 60 * 1000 }, // 60 seconds view: ({ post }) => <Article post={post} />, })

After maxAge the cache is stale: the next request gets the stale value immediately, and a background revalidation refreshes it. Same shape as Next's ISR; no Vercel runtime required.

ts
// Typed search params with shape() validation. import { page, shape, useSearch } from '@place/component' page('/posts', { search: shape({ page: 'number', tag: 'string?' }), view: (props) => { const { page: p, tag } = useSearch<{ page: number; tag?: string }>(props) return <PostList page={p} tag={tag} /> }, })

resource() — client-only

ts
// Client-side fetch with reactive status. Auto-disposes on unmount. // The loader receives an AbortSignal — forward it so stale fetches // are cancelled at the network layer. import { resource } from '@place/reactivity' const data = resource((signal) => fetch('/api/health', { signal }).then((r) => r.json()), ) // The value read is the callable resource itself — data(), not // data.value(). It returns the resolved value, or undefined while // loading / on error. .loading() / .error() / .status() are the // reactive status accessors. <div> {() => data.loading() && <Spinner />} {() => data.error() && <p>{String(data.error())}</p>} {() => data() && <Status v={data()} />} </div> // Or switch on the discriminated status — the cleanest shape: {() => { const s = data.status() if (s.state === 'loading') return <Spinner /> if (s.state === 'error') return <p>{String(s.error)}</p> return <Status v={s.value} /> }}

The Resource itself is the value accessor — call it (data()) to read the resolved value reactively; it returns undefined while loading or on error. Status lives on .loading(), .error(), and the discriminated .status(). There is no .value() method. Use for browser-only fetches that can't run on SSR.

See also