For a correct, instant first paint, the server reads each first-paint named query once and
returns a dehydrated snapshot embedded in the HTML. The read goes through the same authority the
/api/rindle route uses, called in-process (no network hop). The Provider hydrates that seed, then the wasm engine
boots and the live subscribe reconciles onto it. No loading flash, no double fetch.
In production — tantaman.github.io
preloadRindle is a strictly server-side module. Each preloaded (name, args) resolves through
createAppApi in-process. createServerStore(...).preloadAll(...) assembles the dehydrated cache.
A failed read degrades to “no seed for that one query” rather than breaking the page:
// src/ssr.ts — server-only; runs from the route loader
const readInProcess: OneShotQueryFn = async ({ name, args }): Promise<OneShotResult> => {
const api = createAppApi(resolveRindle(process.env));
const context: ApiContext<User> = { user: SSR_USER, request: undefined };
return (await api.handleReadJson({ name, args }, context)) as OneShotResult;
};
/** Preload the given NAMED queries through the authority and return the dehydrated first-paint cache
* to embed in the HTML. Call from a route loader (server only). */
export async function preloadRindle(queries: Array<Query<any, any, any>>): Promise<DehydratedState> {
return createServerStore(schema, { query: readInProcess }).preloadAll(queries, {
onError: (_query, err) =>
console.error("[ssr] preload failed; rendering this query without its first-paint seed:", err instanceof Error ? err.message : err),
});
}
rindle-site/src/ssr.ts L28–42 · tantaman.github.io
The client half of the handoff is a deferred, browser-only boot — createRindleClient dynamically
imports the wasm engine so the SSR shell never evaluates it
(rindle-site/src/rindle-client.ts L45–75).
The client mounts it through the adapter’s Provider.
See also
- TanStack Start — the adapter whose
preloadhook callspreloadRindle. - Preload & navigate — the
ssr:queries that ride into this first paint.