Next.js Integration
This example shows how to use IC Reactor in an existing Next.js app that still
uses dfx for local deployment and declaration generation.
Features Demonstrated
Section titled “Features Demonstrated”- Next.js Pages Router: The app lives under
src/pages, not the App Router. - Motoko Backend: A local todo canister under
backend/. - Manual declaration flow:
dfx generateoutput is committed undersrc/declarations/todo/. - IC Reactor hooks:
createReactorProviderbuilds adefineReactorover those generated declarations once per render tree, and components read its typed hooks withuseTodo().
Setup Guide
Section titled “Setup Guide”cd examples/nextjsnpm run install:allnpm run dfx:startnpm run deploynpm run generatenpm run devKey Integration
Section titled “Key Integration”The snippets import with relative paths. The demo’s own files import the same
modules bare (declarations/todo, service/provider), which its tsconfig.json
resolves through paths: { "*": ["./src/*"] }.
import { createReactorProvider, defineReactor } from "@ic-reactor/react"import { canisterId, idlFactory } from "../declarations/todo"import type { _SERVICE } from "../declarations/todo/todo.did"
export const { ReactorProvider: ICReactorProvider, useReactor: useTodo } = createReactorProvider(() => defineReactor<_SERVICE>({ name: "todo", canisterId, idlFactory, agentOptions: { host: process.env.NEXT_PUBLIC_IC_HOST || "http://127.0.0.1:4943", }, }) )The factory runs once per mounted tree, and a server render is its own tree,
so each request gets its own QueryClient, ClientManager, reactor and
AuthenticationManager. Components take their hooks from useTodo(), which is
typed as defineReactor’s result, so useActorQuery keeps its own signature:
functionName is checked and data is typed without a wrapper or a cast.
import { useTodo } from "../service/provider"
const Todos = () => { const { useActorQuery } = useTodo() const { data, isPending } = useActorQuery({ functionName: "getAllTodos", refetchInterval: 5000, }) // data: Todo[] | undefined}Then wrap the app once, and keep anything that touches the reactor below it.
The provider also renders a QueryClientProvider for the reactor’s
QueryClient, which the React Query Devtools read:
import type { AppProps } from "next/app"import { ReactQueryDevtools } from "@tanstack/react-query-devtools"import { ICReactorProvider } from "../service/provider"
const App: React.FC<AppProps> = ({ Component, pageProps }) => ( <ICReactorProvider> <Component {...pageProps} /> <ReactQueryDevtools initialIsOpen={false} position="bottom" /> </ICReactorProvider>)Nothing waits for clientManager.initialize() before rendering: on a local
replica the agent fetches the root key before its first request, and
useAuth() initializes the agent while it restores the session. The pages
therefore render on the server as well, and the static export carries their
HTML: the todo list in its loading state and the login button in the
isAuthenticating state the auth hooks show until the session is read.