Skip to main content
This is an end-to-end walkthrough for a fresh Next.js 14+ App Router project. Every file you need lives below - copy it, adjust the persona, ship.

Assumptions

  • Next.js 14.x or later, App Router (app/ directory).
  • Node 20+ (for Response.json, fetch).
  • An LLM API key (OpenAI, Grok, LM Studio, or any other OpenAI-compatible endpoint).
  • Optional: a pbt_… bot token if you want dashboard analytics.

1. Install

The package’s peer deps (react, react-dom) come from your existing Next.js install.

2. Environment variables

Create .env.local at the project root (never commit this file):
Add .env.local to .gitignore if it isn’t already.

3. Server-side chat proxy

Create app/api/probot-chat/route.ts. This route holds the LLM key and is the only place your web app calls the provider:
Use export const runtime = "nodejs"; if you’re on Vercel and your LLM responses might take longer than the edge runtime’s response budget. The Node runtime has a much longer default timeout.

4. Client widget

Create components/BotWidget.tsx:
Notice: sendMessage passes the AbortSignal through so a component unmount (e.g. route change while a reply is in flight) cancels the pending fetch cleanly.

5. Mount into the root layout

Mount the widget once, in app/layout.tsx, so every page carries the floating chat bubble:
That’s it - npm run dev, visit http://localhost:3000, click the chat bubble.

Loading the knowledge base from a file

Inlining a multi-KB string bloats the client bundle. Store the knowledge as a JSON or Markdown file and import it:
If the knowledge is genuinely large (100+ KB), pull it server-side via a server component that passes it as a prop to the client widget - keeps your first-paint JS budget lean.

Restricting the proxy to your origin

Next.js API routes accept cross-origin requests by default. If your bot’s knowledge is sensitive, add a same-origin check:
For an embed-anywhere widget, add CORS headers instead of blocking:

Adding per-visitor rate limiting

Every visitor turn = one LLM call = one bill. Rate-limit the proxy per IP:
Both Upstash Redis + Ratelimit have generous free tiers; free tier is plenty for a portfolio bot.

Streaming responses

The stock createOpenAIHandler is non-streaming (returns the full reply after the LLM finishes). If you want typewriter-style streaming, implement sendMessage yourself with an incremental TransformStream:
You’d then swap the built-in <ProbotBot /> for the headless useProbotChat hook and drive tokens in yourself. See the React example for the hook API.

Testing locally

If the curl returns { "reply": … }, the widget will work. If it returns { "error": … }, fix the proxy first - the widget won’t magically make a broken server route work.

Deploying

Vercel:
  1. Set env vars in the Vercel dashboard: OPENAI_API_KEY (encrypted) and NEXT_PUBLIC_PROBOT_TOKEN (public).
  2. Push to your production branch.
Any other host (Fly, Railway, self-managed VPS):
  1. Set the same env vars in your platform’s secrets store.
  2. Ensure the process running Next.js has network egress to the LLM provider (api.openai.com, api.x.ai, etc.).
  3. Confirm /api/probot-chat responds under production DNS before flipping traffic.

Full example repo

The above files map to a working Next.js 14 project. If you want a scaffolded starter with all this pre-wired, see the packages/probot-self-hosted directory in the ProBot monorepo - the package README’s Quick example covers the same setup in a condensed form.