SSR and hydration
This page shows server rendering with the Next.js App Router. The same rules hold in any framework that renders React on a server. The Next.js SSR example is a complete app that does all of it, and each section below names the file it comes from.
The rules:
- One client per request. Never build the client at module scope on a server: a module-scope client is shared by every request.
- The provider’s factory lives in a
"use client"module. A function cannot cross from a Server Component into a client component. - A server renders; it does not run. The auth factory is never called,
nothing reads
windoworlocalStorage, and no effect runs. - The server is anonymous. It prefetches as the anonymous principal, and the browser’s hydrating render uses the same principal, so the keys match.
- A read through
queryOptionsis never retried on a server. Nor is any read of the client’sQueryClient. A direct call still re-sends a retryable read up to twice.
One client per request
Section titled “One client per request”React’s cache() memoizes for the length of one server request. Every Server
Component of the request gets the same client, and the next request gets a new
one:
import { createClient, type Client } from "@ic-reactor/core"import { cache } from "react"
export const requestClient = cache((): Client => createClient({ network: "ic", identity: "anonymous" }))It is anonymous because a server holds no session, and an anonymous client
refuses every update before sending it. There is nothing to dispose: it built
no auth, and a server’s QueryClient sets no timers.
Outside a request, in a script or a plain test, every call of a cache()
function builds a new client. A Route Handler builds one client for each
request it serves, and calls the canister directly: a direct call never
touches a cache (src/server/balance-route.ts in the example).
The provider
Section titled “The provider”The layout is a Server Component. It renders a client module that holds the provider:
"use client"import { createClient } from "@ic-reactor/core"import { ReactorProvider } from "@ic-reactor/react"import { AuthClient } from "@icp-sdk/auth/client"import type { ReactNode } from "react"
export function Providers({ children }: { children: ReactNode }) { return ( <ReactorProvider client={() => createClient({ network: "ic", auth: () => new AuthClient() }) } > {children} </ReactorProvider> )}import type { ReactNode } from "react"import { Providers } from "@/app/providers"
export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body> <Providers>{children}</Providers> </body> </html> )}