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. - Rust Backend: A local canister under
backend/. - Manual declaration flow:
dfx generateoutput is committed undersrc/declarations/todo/. - IC Reactor hooks:
createActorHookswraps aReactorbuilt from those generated declarations.
Open in StackBlitzEdit and inspect the example
View on GitHubBrowse the source code
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”import { createContext, useContext, useState } from "react"import type { ReactNode } from "react"import { QueryClient, QueryClientProvider } from "@tanstack/react-query"import { ClientManager, AuthenticationManager, Reactor, createActorHooks, createAuthHooks,} from "@ic-reactor/react"import { canisterId, idlFactory } from "declarations/todo"import type { _SERVICE } from "declarations/todo/todo.did"
function createReactorContext() { const queryClient = new QueryClient()
const clientManager = new ClientManager({ queryClient, agentOptions: { host: process.env.NEXT_PUBLIC_IC_HOST || "http://127.0.0.1:4943", }, }) const authentication = new AuthenticationManager({ clientManager }) const todoReactor = new Reactor<_SERVICE>({ name: "todo", clientManager, canisterId, idlFactory, })
return { queryClient, clientManager, authentication, todoReactor, auth: createAuthHooks(authentication), todo: createActorHooks(todoReactor), }}
type ReactorContextValue = ReturnType<typeof createReactorContext>
const ReactorContext = createContext<ReactorContextValue | null>(null)
export function ICReactorProvider({ children }: { children: ReactNode }) { // Runs once per mounted tree — and a server render is its own tree, so each // request gets its own managers and its own cache. const [value] = useState(createReactorContext)
return ( <QueryClientProvider client={value.queryClient}> <ReactorContext.Provider value={value}> {children} </ReactorContext.Provider> </QueryClientProvider> )}
export function useICReactor() { const context = useContext(ReactorContext) if (!context) { throw new Error("useICReactor must be used inside <ICReactorProvider>") } return context}Bind the hooks with the hook’s own function type rather than a
Parameters<...> wrapper — useActorQuery is generic over the method name, and
a rest-args wrapper collapses it to the base signature, degrading data to
unknown at every call site:
type TodoHooks = ReturnType<typeof createReactorContext>["todo"]
export const useQueryTodo: TodoHooks["useActorQuery"] = (...args: any[]) => (useICReactor().todo.useActorQuery as any)(...args)Then wrap the app once, and keep anything that touches the reactor below it:
const App: React.FC<AppProps> = (props) => ( <ICReactorProvider> <AppShell {...props} /> </ICReactorProvider>)