TanStack Start

eve/tanstack runs a TanStack Start frontend and an eve agent as one project. The eveTanStack() Vite plugin puts both on one dev server and one Vercel deploy. useEveAgent from eve/react finds the mounted routes on its own, so you configure no CORS or URL env vars.

Prerequisites

  • The eve package installed in your project (npm install eve@latest).
  • An existing eve agent directory. If you don't have one, start from Getting started.
  • A TanStack Start app using React, with the Nitro Vite plugin (nitro installed and nitro() registered). TanStack Start deploys to Vercel through Nitro, and the plugin attaches eve to it.

Add the generated Web Chat app

Run the Web Chat installer and choose TanStack Start when it asks which framework to use:

eve add channel/web

In automation, answer the framework and hosting questions up front. Omit web-framework to install Next.js:

eve add channel/web --non-interactive \
  --answer 'web-framework="tanstack"' \
  --answer 'web-hosting="vercel"'

The installer creates a TanStack Start app under apps/web/, adds the dev:web and build:web scripts, and asks how to deploy it:

  • Vercel services (recommended) deploys Web Chat and the agent as separate services. Run both locally with pnpm dev:all, without linking a Vercel project.
  • TanStack Start hosts the agent in the TanStack Start app through eveTanStack(). Run it with pnpm dev:web. A Vercel deploy from the project root doesn't build the app, because the app's dependencies are in the root package.json. Choose Vercel services to deploy Web Chat on Vercel.

To switch hosting modes, rerun eve add channel/web --skip-install with the other choice. The installer removes the vercel.ts and scripts it wrote for the previous mode. An existing Web Chat app in apps/web/ keeps its framework, and the installer stops if you answer a different one. To switch between Next.js and TanStack Start, remove apps/web/ first.

Sign in with Vercel setup is available only for the Next.js Web Chat app, so the TanStack Start installer doesn't ask about authentication.

The chat uses the same routes as the Next.js Web Chat: /, /s, and /s/$sessionId, one URL per session. Before accepting production browser traffic, replace the generated placeholder authorization policy. See Authenticate browser requests.

A workspace agent always deploys as a separate Vercel service, because the installer doesn't support single-app hosting for it. If you already have a TanStack Start app, skip the installer and follow the steps below.

Register the Vite plugin

Add eveTanStack() to vite.config.ts next to nitro():

vite.config.ts
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { eveTanStack } from "eve/tanstack";
import { nitro } from "nitro/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [eveTanStack(), tanstackStart(), viteReact(), nitro()],
});

The plugin looks for an agent/ folder in the TanStack Start project root, mounted at /eve/v1/*. An agents/ workspace mounts each member at /eve/<name>/v1/* instead. Pass eveRoot when the agent lives elsewhere:

export default defineConfig({
  plugins: [
    eveTanStack({
      eveRoot: "../my-agent",
    }),
    tanstackStart(),
    viteReact(),
    nitro(),
  ],
});

Without nitro() registered, Vite fails at startup with an error naming the missing plugin.

eveTanStack options

All fields are optional.

OptionTypeDefaultPurpose
eveRootstringTanStack Start project rooteve project root, relative to the TanStack Start project root unless absolute.
eveBuildCommandstringgeneratedBuild command for the generated eve Vercel service.
devServerTimeoutMsnumber180000Maximum time to wait for the eve development server to start, including when another TanStack Start process is starting it.

For slow cold starts, increase the development timeout:

export default defineConfig({
  plugins: [
    eveTanStack({
      devServerTimeoutMs: 300_000,
    }),
    tanstackStart(),
    viteReact(),
    nitro(),
  ],
});

Call the hook

With the plugin registered, components call useEveAgent from eve/react and don't pass baseUrl:

src/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { useEveAgent } from "eve/react";
import { useState } from "react";

export const Route = createFileRoute("/")({
  component: Chat,
});

function Chat() {
  const agent = useEveAgent();
  const [draft, setDraft] = useState("");
  const isBusy = agent.status === "busy";
  const isInputDisabled = isBusy || agent.status === "resuming";

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const text = draft.trim();
        if (text.length === 0 || isInputDisabled) return;
        setDraft("");
        void agent.send(text);
      }}
    >
      {agent.messages.map((message) => (
        <article key={message.id}>
          <header>{message.role}</header>
          {message.parts.map((part, index) =>
            part.type === "text" ? <p key={part.id ?? index}>{part.text}</p> : null,
          )}
        </article>
      ))}
      <input
        disabled={isInputDisabled}
        onChange={(event) => setDraft(event.currentTarget.value)}
        value={draft}
      />
      <button disabled={isInputDisabled} type="submit">
        Send
      </button>
    </form>
  );
}

The browser still needs a production authentication policy. See Authenticate browser requests for the default policy, which rejects unverified requests, and for channel configuration.

eve ships hook bindings for React, Vue, and Svelte. A TanStack Start app that uses Solid can mount the agent with this plugin and call it through the Client SDK.

Dev vs deploy topology

  • Local dev. npm run dev starts an eve dev server for each agent, and Nitro proxies each agent's routes to it. The browser talks only to the TanStack Start origin. The plugin uses the server named by EVE_BASE_URL when set. A server the plugin starts stops when the Vite dev server closes (Vite 8.3 or later; earlier versions stop it when the process exits). Any tool that loads vite.config.ts in serve mode, such as Vitest, starts or reuses this server. To avoid that, keep eveTanStack() out of a separate test config.

  • Vercel. The TanStack Start app and the eve runtime deploy as a single project. On Vercel builds, the plugin adds an eve service per agent and routes its transport requests to it. These routes run before filesystem routing and before any routes you declare in nitro({ vercel: { config } }). The TanStack Start app serves everything else, and no vercel.json is required. By default, each generated service runs the installed eve binary from its agent root, so agent directories don't need their own package.json. When the agents need their own build step, set eveBuildCommand:

    export default defineConfig({
      plugins: [
        eveTanStack({
          eveBuildCommand: "npm run build:eve",
        }),
        tanstackStart(),
        viteReact(),
        nitro(),
      ],
    });
  • Other hosts. vite preview and a self-hosted node .output/server/index.mjs don't mount the eve routes. Run the eve service on its own origin and pass baseUrl directly to useEveAgent:

    const agent = useEveAgent({
      baseUrl: "https://agent.example.com",
    });

    eve build and vite build (with Nitro's default Node.js preset) both write .output/. When the agent lives in the TanStack Start project root, the two outputs overwrite each other. Build and serve eve from a separate directory instead, and set eveRoot to it. See Self-host eve for running the eve service.

Managing vercel.json yourself

When vercel.json declares services, the plugin generates nothing, and your configuration controls routing. It must include an eve service (framework: "eve") per agent and a rewrite that sends its requests to it, or the build fails. An agents/ workspace needs one service per member, and a rewrite for /eve/<name>/v1/*:

vercel.json
{
  "services": {
    "web": { "root": ".", "framework": "tanstack-start" },
    "eve": { "root": "agent", "framework": "eve", "buildCommand": "eve build" }
  },
  "rewrites": [{ "source": "/eve/v1/(.*)", "destination": { "service": "eve" } }]
}