---
title: Ship It
description: Part 8 of the Build an Agent tutorial. Put a web dashboard on the agent with useEveAgent, replace placeholderAuth, and deploy to Vercel.
url: "https://eve.dev/docs/tutorial/ship-it"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The analytics assistant runs in the TUI. Next, add a web dashboard and deploy a private, single-user version on Vercel, protected by a username and password with no separate authentication service.

## Add the Web Chat app

Step 1 scaffolded the agent without a web frontend. From the `analytics-assistant/` directory, run `eve add channel/web` and choose **Next.js** when it asks which framework to use:

```bash
npx eve add channel/web
```

This adds a Next.js app (`next.config.ts`, `app/page.tsx`, `app/_components/`) wired to the existing eve channel, plus the chat UI components and their dependencies. Run `npm install` afterward. The generated `next.config.ts` wraps your config with `withEve`, which wires the eve routes:

```ts title="next.config.ts"
import type { NextConfig } from "next";
import { withEve } from "eve/next";

const nextConfig: NextConfig = {};

export default withEve(nextConfig);
```

## Use the generated chat

Keep the generated `app/_components/agent-chat.tsx` and `agent-message.tsx` components. `app/page.tsx` already renders the chat, and `useEveAgent` handles session creation and streaming.

The generated UI displays tool results and errors, authorization links, approval buttons, and question forms. The spend approval from [Guard the spend](./guard-the-spend) needs those controls to resume a waiting turn, so a component that renders only text leaves it stuck.

Run `npm run dev`, open the local web URL the server prints, and ask for an unfiltered revenue query. Confirm that the approval appears and that approving it lets the query finish. To customize the UI afterward, see [Frontend](../guides/frontend/overview).

## Replace `placeholderAuth`

The scaffold's channel ships with `placeholderAuth()`, which rejects unauthenticated production requests. Replace it with a verifier that checks credentials on every request; never return a fixed user without checking the request.

Create `agent/lib/auth.ts`. eve's HTTP Basic verifier compares passwords in constant time, and missing environment variables, missing credentials, and wrong credentials all fail closed:

```ts title="agent/lib/auth.ts"
import { verifyHttpBasic, withAuthChallenges } from "eve/channels/auth";

export const appAuth = withAuthChallenges(
  (request: Request) => {
    const username = process.env.ANALYTICS_USERNAME;
    const password = process.env.ANALYTICS_PASSWORD;
    if (!username || !password) return null;

    if (request.headers.has("origin") && request.headers.get("sec-fetch-site") !== "same-origin")
      return null;

    const result = verifyHttpBasic(request.headers.get("authorization"), { username, password });
    if (!result.ok) return null;

    return {
      ...result.sessionAuth,
      attributes: { team: "growth" },
      issuer: "analytics-tutorial",
    };
  },
  [{ scheme: "Basic", parameters: { realm: "analytics", charset: "UTF-8" } }],
);
```

Replace `agent/channels/eve.ts`, dropping the `devTeam` entry from Step 6. The eve channel checks `appAuth` for session creation, messages, controls, and streams; the other helpers keep authenticated Vercel CLI access and local development:

```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";
import { appAuth } from "../lib/auth";

export default eveChannel({
  auth: [appAuth, vercelOidc(), localDev()],
});
```

The verified username becomes the signed-in user. The `growth` team selects the sample playbook from [Team playbooks](./team-playbooks). Keep these credentials private to one person, because a shared password gives no one a separate identity or isolated sessions. For a multi-user app, use a real session provider and check [which user each session belongs to](../guides/auth-and-route-protection#what-reaches-ctxsessionauth).

Add `proxy.ts` at the project root so opening the dashboard triggers the browser's username/password prompt. It protects only the generated UI routes; eve API routes keep their channel auth, so OIDC-authenticated CLI requests still work:

```ts title="proxy.ts"
import { routeAuth } from "eve/channels/auth";
import { NextResponse } from "next/server";
import { appAuth } from "./agent/lib/auth";

export async function proxy(request: Request) {
  if (process.env.NODE_ENV === "development") return NextResponse.next();
  const result = await routeAuth(request, appAuth);
  return result instanceof Response ? result : NextResponse.next();
}

export const config = { matcher: ["/", "/s/:path*"] };
```

After login, the browser sends the credentials on same-origin requests from the generated chat. When a browser sends an `Origin` header, `appAuth` also requires [`Sec-Fetch-Site: same-origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site). Keep the auth module server-side: never put the password in a React component, a `NEXT_PUBLIC_` variable, or a client-side `useEveAgent` option. Use HTTPS for deployed HTTP Basic authentication.

## Deploy to Vercel

From `analytics-assistant/`, link a Vercel project and add a username and a long, unique password. These commands prompt for values without putting the password in shell history:

```bash
npx vercel@latest link
npx vercel@latest env add ANALYTICS_USERNAME preview
npx vercel@latest env add ANALYTICS_PASSWORD preview
```

Before deploying, configure your model credential in the project's Preview environment, because deployment doesn't upload local `.env` values. If you used a local ChatGPT subscription, which stays on your laptop, switch `agent/agent.ts` to an AI Gateway model and configure `AI_GATEWAY_API_KEY`. See [Deployment](../guides/deployment/overview) for model and runtime configuration.

```bash
npx vercel@latest deploy
```

Open the HTTPS preview URL, enter the username and password, and ask a sample-data question. In a private browser window, cancel the login prompt and verify the dashboard is denied. A `POST /eve/v1/session` request without credentials must also return `401`.

The web app and the agent share one origin, and the sandbox runs on Vercel Sandbox. You can also smoke-test the deployment through the authenticated CLI:

```bash
npx eve remote connect --url https://your-analytics-app.vercel.app
```

For a production deployment, add the username, password, and model credential to the Production environment too, then run `npx vercel@latest deploy --prod`. Missing Basic credentials keep browser access closed.

The private assistant queries the sample data, runs analysis in a sandbox, and charts the results. It also remembers definitions, loads the Growth playbook, and asks before an expensive query.

## What you learned

Across the eight steps you built and shipped one agent using:

- **Tools** to give the model typed actions (`run_sql`, `chart_series`, `define_metric`).
- **The sandbox** to compute and chart beyond SQL in an isolated `/workspace`.
- **State** (`defineState`) to remember the team's glossary across turns.
- **Dynamic skills** (`defineDynamic`) to load the right team playbook per caller.
- **Human-in-the-loop** approval (`approval`) to gate expensive queries.
- **Channel auth** to turn a request into a signed-in user.
- **Deployment** to Vercel, with the agent behind your web app.

## Next steps

- [Connect a warehouse](./connect-a-warehouse) when you have a data service to use in place of the sample dataset.
- [MCP connections](../connections/mcp) for tool allowlists and per-connection approval.
- [Sandbox](../sandbox) for backends, when sandboxes start and stop, and network policy.
- [Dynamic capabilities](../guides/dynamic-capabilities) for schema-derived dynamic tools, a read-only analyst subagent, and model-authored report workflows on this same example.
- [Authentication](../guides/auth-and-route-protection) for production auth patterns.

Learn more: [Frontend](../guides/frontend/overview) · [Authentication](../guides/auth-and-route-protection) · [Deployment](../guides/deployment/overview)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)