Skip to content

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 { icReactorPlugin } from "@ic-reactor/vite-plugin"
export default defineConfig({
plugins: [
icReactorPlugin({
canisters: [
{
name: "backend",
didFile: "src/declarations/backend.did",
},
],
}),
],
})

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

src/reactor/index.ts
import { ClientManager } from "@ic-reactor/react"
import { queryClient } from "./queryClient"
export const clientManager = new ClientManager({
queryClient,
// Automatically detects network and canister IDs from ic_env cookie
})
  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.

1. Setup ClientManager with withProcessEnv

Section titled “1. Setup ClientManager with withProcessEnv”
export const clientManager = new ClientManager({
// Auto-detects network based on DFX_NETWORK env var
withProcessEnv: true,
})

Create .env.local for local development:

.env.local
DFX_NETWORK=local
VITE_BACKEND_CANISTER_ID=bkyz2-fmaaa-aaaaa-qaaaq-cai

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 "./reactor"
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. Run dfx info webserver-port to verify the port.

  2. If it’s not 4943, update your ClientManager config:

    export const clientManager = new ClientManager({
    port: 8000, // or your specific port
    withProcessEnv: true,
    })

To use Internet Identity locally, you need to deploy a local instance of the II canister.

  1. Pull the dependency in dfx.json:

    {
    "canisters": {
    //...
    "internet_identity": {
    "type": "custom",
    "candid": "https://github.com/dfinity/internet-identity/releases/latest/download/internet_identity.did",
    "wasm": "https://github.com/dfinity/internet-identity/releases/latest/download/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 if withProcessEnv is set correctly!

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