Next.js SSR
A Next.js App Router app built on @ic-reactor/core and @ic-reactor/react
with TanStack Query’s own hooks. It reads the ICP, ckBTC and ckETH ledgers on
mainnet through one module generated from icrc1.did by candid-core-cli gen,
and shows the server-rendering story end to end:
- One client per request. A Server Component builds
createClient({ network: "ic", identity: "anonymous" })inside the request (React’scache()), prefetches every ledger’s name, symbol, decimals, fee, total supply, minting account and metadata with{ id }targets, and handsdehydrate(client.queryClient)to a<HydrationBoundary>. - The provider factory in a
'use client'module.<ReactorProvider client={() => createClient({ network: "ic", auth: () => new AuthClient() })}>builds a client per mounted tree. The server render and the browser’s hydrating render build keys for the anonymous principal, so every visitor gets the server’s keys, a tab still signed in from an earlier visit included, and the cards hydrate from them with nothing fetched on load. - Lossless hydration.
bigintamounts, principals andUint8Arrayvalues arrive exact; the page prints each value’s run-time type. - No JavaScript needed to read the pages: the values are in the server’s
HTML, which a smoke script checks against a running
next start. - Streaming. On the account page, certified balances (replicated calls)
stream in after the rest of the page, from an async Server Component inside
<Suspense>. - Progressive enhancement. The account lookup is a GET form validated on
the server with
isPrincipalandprincipal(). - A Route Handler with its own client per request answers JSON, with
amounts as decimal text and base units, and maps a
ReactorErrorto an HTTP status by itskind, readinghttpStatuswhere the kind is not enough: a canister that does not exist is a 502, not a 503 that invites a retry. - Errors on the server. A canister that is not a ledger renders its own
section with the error’s
kind; the rest of the page renders as usual. - Sign-in after hydration. Signing in with Internet Identity moves every read to keys that hold the user’s principal; nothing read anonymously is reused for them. A tab that reloads signed in hydrates without a mismatch, then reads its own keys the same way.
The tests run the real client on createTestClient() from
@ic-reactor/core/testing, with the ledgers mocked over in-memory replicas,
and never reach mainnet.
The SSR and hydration guide explains each rule, and Auth covers sign-in and the caller.
StackBlitz installs the example from npm and runs the tests (npm test):
you can read and change the code there and run them again. It cannot serve
the app. Next 16 keeps each request’s state in Node’s AsyncLocalStorage,
which StackBlitz does not implement, so next dev fails there on every page
with Next’s own “Invariant: Expected workStore to be initialized”, and a
production build renders through the same storage. Clone the repository to
run the app.