React SPA
Host Copilot Runtime for a React single-page app that has no server of its own.
The rest of these docs are the React docs. Frontend tools, generative UI, human-in-the-loop, headless UI, prebuilt components and the React reference all work unchanged in a Vite or Create React App project.
Exactly one instruction does not carry over: where Copilot Runtime lives. The quickstarts assume Next.js serves your app and the runtime from one origin, so they use a relative runtimeUrl of /api/copilotkit. A single-page app has no server and no shared origin, so that path resolves to nothing. This page covers that one difference and sends you back to the pages above for everything else.
Prerequisites#
- An OpenAI API key (or another model provider supported by Model Selection)
- React 18+
- Node.js 20+
Getting started#
Create your React app#
If you don't have one already:
npm create vite@latest my-copilot-app -- --template react-ts
cd my-copilot-app
npm installAn existing Create React App project works the same way — only the dev server command in the last step differs.
Install CopilotKit#
Install the React frontend package and @copilotkit/runtime for your local Copilot Runtime server:
npm install @copilotkit/react-core @copilotkit/runtime
npm install -D tsx typescript @types/nodepnpm add @copilotkit/react-core @copilotkit/runtime
pnpm add -D tsx typescript @types/nodeyarn add @copilotkit/react-core @copilotkit/runtime
yarn add -D tsx typescript @types/nodeCreate the Copilot Runtime#
Your SPA has no server, so the runtime needs one of its own. Add a small Node server that hosts Copilot Runtime at /api/copilotkit on its own port and registers a default built-in agent:
import { createServer } from "node:http";
import { BuiltInAgent, CopilotRuntime } from "@copilotkit/runtime/v2";
import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node";
const runtime = new CopilotRuntime({
agents: {
default: new BuiltInAgent({
model: "openai:gpt-5-mini",
prompt: "You are a helpful assistant for a React app.",
}),
},
});
const port = 8200;
createServer(
createCopilotNodeListener({
runtime,
basePath: "/api/copilotkit",
cors: true,
}),
).listen(port, () => {
console.log(
`Copilot Runtime listening at http://localhost:${port}/api/copilotkit`,
);
});cors: true is required here, and it is not the default
Your app and your runtime are on different origins, so the runtime has to opt into CORS. createCopilotNodeListener and createCopilotRuntimeHandler are off by default — omit cors and every browser request fails preflight. This differs from the Express and Hono adapters, which default to permissive CORS. See Runtime endpoints for per-origin and credentialed configuration before you deploy.
Import the styles#
Import the package stylesheet once in your app entry. It's self-contained, so the chat renders without any other CSS.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "@copilotkit/react-core/v2/styles.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);Connect to Copilot Runtime#
Point CopilotKitProvider at the runtime endpoint and drop in CopilotChat. Because the runtime registers an agent named default, the chat picks it up automatically.
import { CopilotKitProvider, CopilotChat } from "@copilotkit/react-core/v2";
export default function App() {
return (
<CopilotKitProvider runtimeUrl="http://localhost:8200/api/copilotkit">
<div style={{ height: "100vh" }}>
<CopilotChat />
</div>
</CopilotKitProvider>
);
}The runtimeUrl must be absolute
Every framework quickstart uses a relative runtimeUrl="/api/copilotkit". That works only because Next.js serves the app and the runtime from the same origin. In a single-page app the runtime is a separate process on a separate port, so the URL has to name it in full — host and port included. Read it from an env var (import.meta.env.VITE_COPILOT_RUNTIME_URL in Vite) so you can point it at your deployed runtime in production.
Pick your chat layout
CopilotChat is a full-height chat. Swap it for CopilotSidebar (a collapsible side panel) or CopilotPopup (a floating widget) for a different layout. They take the same props.
Run the runtime and app#
You are running two dev servers, so they need two different ports. Start Copilot Runtime in one terminal:
export OPENAI_API_KEY=sk-...
npx tsx server.tsStart the React app in another terminal:
npm run devVite serves on http://localhost:5173 and the runtime on 8200, so the defaults don't collide. If you need to move the app, pass --port — Vite ignores the PORT environment variable, unlike Create React App:
npm run dev -- --port 3000Open the dev server URL, send a message, and you'll see it stream back through Copilot Runtime.
Troubleshooting
- CORS errors, or requests failing on preflight: Keep
cors: trueincreateCopilotNodeListener. It is off by default, and this is the most common cause of a chat that renders but never responds. - 404s on
/api/copilotkit: YourruntimeUrlis relative. A SPA needs the absolutehttp://localhost:8200/api/copilotkit. - No response from the agent: Confirm the runtime server is running and
http://localhost:8200/api/copilotkit/inforeturns agent information. - Chat renders unstyled: Make sure you imported
@copilotkit/react-core/v2/styles.cssin your app entry. - Model auth errors: Confirm
OPENAI_API_KEYis set in the terminal runningnpx tsx server.ts.
Open Inspector and confirm setup#
On localhost, click the Inspector button in the corner of the app.
- Open Agents, then Agent. Your agent is listed.
- Send a chat message. Open Agents, then AG-UI Events. Events are moving.
- Open Threads. The list is unlocked (Intelligence is on), or locked with Enable Intelligence (Intelligence is off).
More detail: Inspector.
Next steps#
The runtime is the only SPA-specific piece. From here the root React docs apply as written:
- Frontend tools — let the agent call into your app
- Generative UI — render your own components from agent output
- Human-in-the-loop — pause a run for user approval
- Headless UI — build your own chat surface
- Prebuilt components — the chat, sidebar and popup layouts
- React reference — full provider, hook and component API
To connect an agent framework instead of BuiltInAgent, follow any integration quickstart and keep the server.ts host and absolute runtimeUrl from this page in place of its Next.js route handler.