Bext Query (signals)
Reactive data cache on bext signals — TanStack-shaped API
(createQuery, createMutation, invalidateQueries, dehydrate/hydrate)
without React. The page prefetches /api/poll server-side,
dehydrates the cache into the island marker, and the island hydrates
its client cache before the first createQuery call —
so first paint shows real data, no loading flash.
Output
Manual query (staleTime 5s)
status: success · fetching: no · stale: no
now = 2026-05-03T10:37:51.556Z
Live query (refetchInterval 3s)
tick = —
src/components/QueryDemo.tsx
"use signals";
/** @jsxImportSource @bext-stack/framework/signals */
import {
createQueryClient, createQuery, createMutation,
type DehydratedState,
} from "@bext-stack/framework/query";
export default function QueryDemo(props: { dehydrate?: DehydratedState }) {
const client = createQueryClient();
client.hydrate(props.dehydrate);
const poll = createQuery<{ now: string }>(client, {
queryKey: ["poll"],
queryFn: async ({ signal }) => (await fetch("/api/poll", { signal })).json(),
staleTime: 5_000,
});
return (
<div>
<p>status: {poll.status.value} · fetching: {poll.isFetching.value ? "yes" : "no"}</p>
<p>now = {poll.data.value?.now ?? "—"}</p>
<button onClick={() => poll.refetch()}>refetch</button>
</div>
);
}src/app/examples/query/page.tsx
import { signalsIsland } from "@bext-stack/framework/signals";
import { createQueryClient } from "@bext-stack/framework/query";
import QueryDemo from "../../../components/QueryDemo";
export default async function Page() {
const client = createQueryClient();
// Server-side prefetch — populates the cache before the island renders.
await client.prefetchQuery({
queryKey: ["poll"],
queryFn: async () =>
(await fetch("http://127.0.0.1:3032/api/poll")).json(),
});
return signalsIsland("QueryDemo", QueryDemo, {
dehydrate: client.dehydrate(),
});
}