Add a chatbot to a Next.js site
Goal: a chat button in the bottom-right corner of every page. It opens a panel where visitors can talk to your Nebulas agent. Visitors don’t sign in. Your server calls Nebulas with its own service credentials.
Before you start: install the kit and set up Tailwind, and create the assistant that should answer. Read how the chat picks an assistant so your visitors reach your assistant and not the default one.
What you’ll add
Section titled “What you’ll add”- .env.local
Directorysrc
Directorylib
- nebulas.ts server config, shared by both routes
Directoryapp
- layout.tsx mounts the chat once
Directoryapi
Directorya2a
- route.ts POST: forwards chat messages to the agent
Directoryconfig
- route.ts GET: agent card, project, brand
Directorycomponents
- SiteChat.tsx client component
-
Add your environment variables. This example uses a service account. Any service strategy works; see Authentication.
.env.local A2A_URL=https://api.nebulas.example.com/v1/orchestrator/a2aNEBULAS_API_URL=https://api.nebulas.example.comNEBULAS_API_KEY=neb_xxxxxxxxxxxxxxxxNEBULAS_PROJECT_ID=66b0c1f2e4b0a1c2d3e4f5a6SERVICE_ACCOUNT_ID=...SERVICE_ACCOUNT_KEY_ID=...SERVICE_ACCOUNT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----" -
Configure the server once.
src/lib/nebulas.ts import { configureNebulasServer } from "@gnomondigital/nebulas-kit-core";configureNebulasServer({a2aUrl: process.env.A2A_URL,nebulasApiUrl: process.env.NEBULAS_API_URL,nebulasApiKey: process.env.NEBULAS_API_KEY,nebulasProjectId: process.env.NEBULAS_PROJECT_ID,nebulasAuthStrategy: "service_account",serviceAccount: {serviceAccountId: process.env.SERVICE_ACCOUNT_ID ?? "",keyId: process.env.SERVICE_ACCOUNT_KEY_ID ?? "",privateKeyPem: process.env.SERVICE_ACCOUNT_PRIVATE_KEY,},}); -
Add the chat route. It receives the visitor’s messages, adds your credentials, and streams the agent’s reply back.
src/app/api/a2a/route.ts import "@/lib/nebulas";import { createA2AProxyHandlerWithServiceAuth } from "@gnomondigital/nebulas-kit-core/nextjs";export const POST = createA2AProxyHandlerWithServiceAuth({apiKey: process.env.NEBULAS_API_KEY,}); -
Add the config route. The chat calls it on load to get the agent’s name and description, the project ID, and the brand theme.
src/app/api/a2a/config/route.ts import "@/lib/nebulas";import type { NextRequest } from "next/server";import {handleA2AConfigNextJsGET,createGetHeadersWithServiceAuth,} from "@gnomondigital/nebulas-kit-core/nextjs";const getHeaders = createGetHeadersWithServiceAuth({apiKey: process.env.NEBULAS_API_KEY,});export async function GET(request: NextRequest) {return handleA2AConfigNextJsGET(request, { getHeaders });} -
Create the chat component. It has to be a client component (
"use client").src/components/SiteChat.tsx "use client";import { A2AChatFloating } from "@gnomondigital/nebulas-kit-react";export function SiteChat() {return (<A2AChatFloatinga2aEndpoint="/api/a2a"configEndpoint="/api/a2a/config"panelTitle="Ask our assistant"suggestedPrompts={["What services do you offer?","How can I contact sales?",]}labels={{placeholder: "Type your question…",howCanIHelp: "Hi! How can I help?",}}/>);} -
Mount it once in the root layout so it shows on every page:
src/app/layout.tsx import { SiteChat } from "@/components/SiteChat";import "./globals.css";export default function RootLayout({ children }: { children: React.ReactNode }) {return (<html lang="en"><body className="min-h-screen bg-background antialiased">{children}{/* Floating button, bottom-right */}<SiteChat /></body></html>);} -
Try it. Run
npm run dev, open any page, and click the button in the bottom-right corner.
Customize it
Section titled “Customize it”| You want to… | Do this |
|---|---|
| Open the panel on page load | defaultOpen |
| Let visitors upload PDFs, Word, Markdown or text files | showAttachFileButton |
| Match your brand color on the button | fabClassName="bg-primary text-primary-foreground hover:bg-primary/90" |
| Change the panel size or position | panelClassName="..." |
| Translate the UI | labels={{ title, description, newChat, howCanIHelp, askAbout, placeholder }} and locale |
| Show errors in your own toast | onError={(msg) => toast.error(msg)} |
Keep it inside your React tree instead of document.body |
usePortal={false} |
| Use your Nebulas brand’s colors | Set nebulasBrandName on the server. See Branding. |
See every prop in the component reference.
Show it on some pages only
Section titled “Show it on some pages only”Mount <SiteChat /> in a nested layout instead of the root layout, for example
app/(marketing)/layout.tsx. It then only shows on pages in that group.
Signed-in users instead?
Section titled “Signed-in users instead?”If your site already uses Auth0, replace step 3 with createA2AProxyHandler({ auth0, audience })
and pass user={{ name, picture }} to the component. See
Authentication → Auth0 session.
