Askr
Routing & Data

Loaders and Deferred Values

Use loaders and deferred values with the current published Askr packages.

  • @askrjs/askr0.0.59

How to use loaders and deferred values

Return critical loader data directly and wrap slower independent work in defer so the page can stream a stable shell.

  1. Import the published @askrjs/askr entrypoint.
  2. Keep loaders and deferred values configuration next to the component, route, or server composition root that owns it.
  3. Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { defer } from '@askrjs/askr/router';

route('/projects/:projectId', ProjectPage, {
  loader: async ({ params }) => ({
    project: await projects.get(params.projectId),
    activity: defer(activity.list(params.projectId)),
  }),
});

Route loaders

`RouteOptions.loader` is a function that receives the route's `RouteContext` plus an optional `request`, and its return value becomes the data available to the route on first render — including during SSR, where it runs before the response streams. `preload` is a separate hook meant for warming query caches ahead of render rather than blocking on the result, and it's handed a `QueryPrefetchContext` alongside the usual context fields. Both run per-navigation, keyed to the route that's about to become active, not on some global schedule.

Deferred values

`defer(promise)` wraps a `PromiseLike<T>` into a `Deferred<T>` you can return from a loader without blocking the initial render on it — the page renders immediately and the deferred value resolves in the background. `isDeferred()` checks whether a given value is one of these wrappers, which loaders and data-fetching code use to decide whether to await it directly or hand it to `Resolve`. `routeData<T>()` reads whatever the current route's loader returned, deferred values included, from inside the rendering component.

Pending and rejected states

A `Deferred<T>` exposes its own `state` (`'pending' | 'fulfilled' | 'rejected'`), plus `value` and `error` fields that update as the underlying promise settles. The `Resolve` component is the declarative way to render against that state: give it a `value`, a `pending` fallback, an optional `rejected` fallback (a node or a function of the error), and a `children` render function that only runs once the value is fulfilled. That keeps loading and error UI colocated with the data they describe instead of scattered across manual `if` checks.

Cancellation

Every loader and `preload` call gets a `signal: AbortSignal` on its context, and Askr aborts it automatically if a newer navigation supersedes the one that triggered it — so a slow loader for a route the user already navigated away from doesn't finish late and overwrite current state. `resolveDeferredValues(input, signal)` walks a data structure and resolves any nested `Deferred` values it finds, respecting that same signal so a whole tree of pending data can be cancelled together rather than one promise at a time.