Skip to content

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.

  • .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
  1. 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/a2a
    NEBULAS_API_URL=https://api.nebulas.example.com
    NEBULAS_API_KEY=neb_xxxxxxxxxxxxxxxx
    NEBULAS_PROJECT_ID=66b0c1f2e4b0a1c2d3e4f5a6
    SERVICE_ACCOUNT_ID=...
    SERVICE_ACCOUNT_KEY_ID=...
    SERVICE_ACCOUNT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
  2. 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,
    },
    });
  3. 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,
    });
  4. 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 });
    }
  5. 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 (
    <A2AChatFloating
    a2aEndpoint="/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?",
    }}
    />
    );
    }
  6. 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>
    );
    }
  7. Try it. Run npm run dev, open any page, and click the button in the bottom-right corner.

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.

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.

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.