Skip to content
IC Reactor

Local Development

The modern way to handle local development is using icp-cli and @ic-reactor/vite-plugin. This setup automates environment variables and proxy configuration.

If you’re using Vite, add the plugin to your vite.config.ts. It will automatically proxy /api requests to your local replica and inject canister IDs.

vite.config.ts
import { icReactor } from "@ic-reactor/vite-plugin"
export default defineConfig({
plugins: [
icReactor({
canisters: [
{
name: "backend",
didFile: "src/declarations/backend.did",
},
],
}),
],
})

The ClientManager automatically detects your environment through the ic_env cookie set by the Vite plugin or an asset canister.

Put it where the generated reactors expect it. The plugin’s default clientManagerPath is "../../clients", and its default outDir is src/declarations, so the generated src/declarations/backend/index.generated.ts resolves clientManager from src/clients.ts:

src/clients.ts
import {
AuthenticationManager,
ClientManager,
createAuthHooks,
} from "@ic-reactor/react"
import { QueryClient } from "@tanstack/react-query"
export const queryClient = new QueryClient()
// Network and local canister IDs come from the ic_env cookie automatically
export const clientManager = new ClientManager({ queryClient })
export const authentication = new AuthenticationManager({ clientManager })
export const { useAuth, useAgentState } = createAuthHooks(authentication)
  1. Start the local network:

    Terminal window
    icp network start -d
  2. Deploy your backend:

    Terminal window
    icp deploy backend
  3. Start your dev server:

    Terminal window
    npm run dev

If you’re not using the Vite plugin, you can still configure everything manually.

ClientManagerParameters has four members: the required queryClient, an optional agentOptions (the HttpAgentOptions handed to the underlying HttpAgent), an optional allowEnvConfig, and allowEnvRootKey — the former name of that option, still honoured for the root key alone. Network detection needs no flag — it always runs. allowEnvConfig controls whether the ic_env cookie is trusted at all: its root key, its Internet Identity provider, and the canister IDs a reactor resolves by name. It defaults to true for hosts that are unambiguously a local replica and false everywhere else, so a reactor with no configured canisterId works on localhost and throws on a real domain.

import { ClientManager } from "@ic-reactor/react"
import { QueryClient } from "@tanstack/react-query"
export const queryClient = new QueryClient()
// Served from localhost → the host is inferred from window.location.origin
export const clientManager = new ClientManager({ queryClient })

To point the agent somewhere else, pass the host explicitly:

export const clientManager = new ClientManager({
queryClient,
agentOptions: { host: "http://127.0.0.1:4943" },
})
.env.local
VITE_BACKEND_CANISTER_ID=bkyz2-fmaaa-aaaaa-qaaaq-cai
import { Reactor } from "@ic-reactor/react"
import { idlFactory, type _SERVICE } from "./declarations/backend"
export const backend = new Reactor<_SERVICE>({
clientManager,
idlFactory,
name: "backend",
canisterId: import.meta.env.VITE_BACKEND_CANISTER_ID,
})

When communicating with the IC mainnet, the agent uses a hardcoded public key to verify responses. For a local replica, the agent needs to fetch a different root key during initialization.

import { useEffect } from "react"
import { clientManager, useAgentState } from "./clients"
function App() {
const { isInitialized } = useAgentState()
useEffect(() => {
clientManager.initialize()
}, [])
if (!isInitialized) {
return <div>Loading...</div>
}
return <YourApp />
}

This error usually means the agent hasn’t fetched the local root key.

Solution: Ensure you are calling clientManager.initialize() at the root of your application.

Check if your local replica is running and on which port.

  1. If you use icp-cli, run icp network status -e local --json and check the returned port.

  2. If you use a manual dfx workflow, run dfx info webserver-port.

  3. If the replica is not on 4943, point the agent at it through agentOptions.host:

    export const clientManager = new ClientManager({
    queryClient,
    agentOptions: { host: "http://127.0.0.1:8000" },
    })

Internet Identity on Localhost (manual dfx workflow)

Section titled “Internet Identity on Localhost (manual dfx workflow)”

If you are using a manual dfx project, deploy a local instance of the Internet Identity canister.

  1. Pull the dependency in dfx.json:

    {
    "canisters": {
    //...
    "internet_identity": {
    "type": "custom",
    "candid": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity.did",
    "wasm": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity_dev.wasm.gz",
    "specified_id": "rdmx6-jaaaa-aaaaa-aaadq-cai"
    }
    }
    }
  2. Deploy it:

    It is recommended to deploy it to the default canister ID:

    Terminal window
    dfx deploy internet_identity --specified-id rdmx6-jaaaa-aaaaa-aaadq-cai
  3. Login using the local provider URL. useAuth handles this automatically once the local Internet Identity canister is deployed and the page is served from localhost.

    // Auto-detects based on network
    // Local: http://rdmx6-jaaaa-aaaaa-aaadq-cai.localhost:4943/authorize
    // Mainnet: https://id.ai/authorize
    const { login } = useAuth()