Skip to content

raf-effect-atom and effect-atom-boundary are on npm. Both are pre-1.0, so a minor release can still break them. Install them

raf-effect-atom

Getting started

Examples

The same issue tracker on TanStack Start, on the Next.js App Router and on React Router Framework Mode, live on Vercel and ready to open in CodeSandbox.

The repository has one app built three times: a small issue tracker on TanStack Start, on the Next.js App Router and on React Router Framework Mode, so the integrations can be compared side by side. All three use both packages over the same Effect RPC contract. Each runs live on Vercel, and opens in CodeSandbox, which installs it on a cloud VM and starts its dev server.

FrameworkSourceLiveTry it
TanStack Startexamples/tanstack-starttanstack-start.raf-effect-atom.katt.devOpen in CodeSandbox
Next.js App Routerexamples/nextjsnextjs.raf-effect-atom.katt.devOpen in CodeSandbox
React Router Framework Modeexamples/react-routerreact-router.raf-effect-atom.katt.devOpen in CodeSandbox

CodeSandbox opens the issue list's route file, and the preview shows the running app once the install finishes.

How each one starts atoms

  • TanStack Start: route loaders start atoms with the Loader on the router context, and setupRouterAtoms streams what the server's loaders started into the browser's registry (see TanStack Start). After the first page, loaders run in the browser, so ensure shows what the registry already holds at once.
  • Next.js: server components start atoms with prefetch, ensure and fetch from raf-effect-atom/rsc, and <HydrationBoundary registry={atoms.getRegistry()}> hands them to the client components under it. Every navigation renders on the server in a new request registry, so ensure waits as long as fetch.
  • React Router: loaders start atoms in the request's registry and return atoms.dehydrate(), which the route renders under <HydrationBoundary> (see Hydration). Each route exports the same function as its clientLoader, and clientAtomsMiddleware gives it the browser's registry, so after the first page loaders run in the browser and ensure shows what the registry already holds at once, as on TanStack Start.

The backend reads the session and latency from cookies, so on the server each app seeds the request registry with the page request's cookie, which its RPC protocol sends: makeRegistry({ seed }) on TanStack Start, atomsMiddleware(atomsContext, { seed }) on React Router, and the verbs from makeRequestAtoms({ seed }) on Next.js, whose seed awaits headers().

What they cover

Screen or actionShows
Loader verbThe URL's first segment picks how loaders start atoms: prefetch, ensure or fetch (/ensure/issues/3)
Issue list with status filter and sortSearch params into a loader, the first page in the server's HTML, then infinite scroll with Loader.make(registry).prefetch for the next page
Issue detailUnder prefetch the page opens onto skeletons that stream in; ensure and fetch wait for the issue
Assignee cardThe loader starts the assignee by issue id next to the issue, so neither waits on the other (see Request waterfalls)
Comments under an issueA paged atom that streams in under every verb, and posting a comment with a pending row
Inline-edit title and description; change statusAtomRpc mutations that refresh the queries they touch; the server refuses an empty value, and its message shows under the field
/prefetch/issues/999, or Session set to Signed outError boundaries by tag: IssueNotFound in the issue's boundary, Unauthorized in the auth boundary, and RpcClientError in the app's when the server can't be reached
Progress bar along the topThe router's pending state while ensure and fetch wait

Data comes from an in-memory database with 42 seeded issues and artificial latency (400 ms ± 50% per call), behind the RPC backend the three apps share. The top bar's Loader select switches the loader verb and keeps the page you are on. It also sets the latency, how links prefetch, and whether the session is signed in; Reset settings puts those back. Data the server loads for the first render streams in with the HTML; later loads show up in the browser's network tab as POST /rpc. On the deployed apps, the next request can reach a different server instance with its own copy of the data, so an edit may not stick.

Run them locally

git clone https://github.com/KATT/raf-effect-atom
cd raf-effect-atom
pnpm install
pnpm --filter @examples/tanstack-start dev  # http://localhost:3005
pnpm --filter @examples/react-router dev    # http://localhost:3006
pnpm --filter @examples/nextjs dev          # http://localhost:3007

The codesandbox branch holds a standalone copy of each app, with the libraries and the shared example packages next to it in one pnpm workspace. Clone that branch to run one app outside the monorepo: pnpm install && pnpm dev in examples/<app>.

Demos in these docs

The guides also embed live demos that run entirely in your browser, each with its own registry and a fake server with adjustable latency. Good places to start: