Local Development
Local Development Workflow
Section titled “Local Development Workflow”The modern way to handle local development is using icp-cli and @ic-reactor/vite-plugin. This setup automates environment variables and proxy configuration.
1. Configure Vite Plugin
Section titled “1. Configure Vite Plugin”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.
import { icReactor } from "@ic-reactor/vite-plugin"
export default defineConfig({ plugins: [ icReactor({ canisters: [ { name: "backend", didFile: "src/declarations/backend.did", }, ], }), ],})2. Set Up ClientManager
Section titled “2. Set Up ClientManager”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:
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 automaticallyexport const clientManager = new ClientManager({ queryClient })
export const authentication = new AuthenticationManager({ clientManager })export const { useAuth, useAgentState } = createAuthHooks(authentication)3. Run Development Cycle
Section titled “3. Run Development Cycle”-
Start the local network:
Terminal window icp network start -d -
Deploy your backend:
Terminal window icp deploy backend -
Start your dev server:
Terminal window npm run dev
Manual Configuration (Optional)
Section titled “Manual Configuration (Optional)”If you’re not using the Vite plugin, you can still configure everything manually.
1. Setup ClientManager
Section titled “1. Setup ClientManager”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.originexport 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" },})2. Configure Canister IDs
Section titled “2. Configure Canister IDs”VITE_BACKEND_CANISTER_ID=bkyz2-fmaaa-aaaaa-qaaaq-caiimport { 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,})Fetching Root Key
Section titled “Fetching Root Key”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 />}Troubleshooting
Section titled “Troubleshooting”“Fail to verify certificate”
Section titled ““Fail to verify certificate””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.
“Can’t connect to localhost:4943”
Section titled ““Can’t connect to localhost:4943””Check if your local replica is running and on which port.
-
If you use
icp-cli, runicp network status -e local --jsonand check the returned port. -
If you use a manual
dfxworkflow, rundfx info webserver-port. -
If the replica is not on
4943, point the agent at it throughagentOptions.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.
-
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"}}} -
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 -
Login using the local provider URL.
useAuthhandles 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/authorizeconst { login } = useAuth()