Gaia Domains 101: How to Build a Multi-Domain Chat App (Step-by-Step)

Gaia is a decentralized computing network that enables anyone to create, deploy, scale, and monetize AI that reflect their styles, values, and expertise. Every time someone runs their own Gaia node they get access to Gaia web3 chat UI, OpenAI compatible API and the ability to fully customize your own LLM. Running a single node is cool and amazing but if you attach a domain, it becomes something more meaningful.
That may sound a bit absurd, well think of “ENS(Ethereum Name Service)” – for web3 wallet addresses, it gives users their own unique identity, customization and discoverability.
Currently when you run your own Gaia node, you’ll get string of address similar to this one https://0xf63939431ee11267f4855a166e11cc44d24960c0.gaia.domains
but it kind of doesn't make sense. If you go ahead and attach a domain to it, it becomes more recognizable. For example: metamask.gaia.domains
– with this, you would know instantly what it’s about and what’s the specialized use-case would be.
Now imagine having multiple specialized domains and having an application that can quickly swap any domains as needed, rather than swapping models like normal AI chat applications. It will be almost like, “swap the domain, swap the brain” - because each domain is in it’s own customizable specialized domain with fine-tuned LLM running. With Gaia you can host your own large‑language model and swap entire knowledge bases just by choosing a different domain. This post walks you from zero to running a chat app that lets users pick among multiple Gaia Domains, each powered by an LLM.
Gaia — a decentralized inference network that allows anyone to spin up their own AI models, host them under a domain, and expose them as OpenAI-compatible endpoints. And in this post, we’re going to build a full-stack chat application that lets users switch between those domains as easily as changing tabs.
What Are We Building?
We’re building a multi-domain AI chat interface. Think of it as a ChatGPT clone—but with a twist. Instead of selecting “GPT-3.5” or “Claude 3,” users will choose Gaia domains, each representing a different LLM hosted on the decentralized Gaia network.
Each domain might run a different model (LLaMA, Deepseek, Mistral) or even be customized for a specific use case like DeFi support or smart contract debugging.
Here’s what the core UX looks like:
- A sidebar to start or switch chats
- A chat pane with streaming tokens
- A dropdown to switch Gaia domains
- A settings modal to plug in your Gaia API key
Solution Overview
We’re using Vercel AI SDK. The beauty of this setup is it allows colocating API routes and pages, simplifying full-stack development.
Prerequisites
To follow along, you’ll need:
- Node.js ≥ 18
- A Gaia API key, which you can get from https://www.gaianet.ai/
- A package manager (pnpm, yarn, or npm). We’ll use pnpm.
Bootstrap the Project
Open your terminal and run:
npx create-next-app@latest gaia-domain-chat --ts --app
cd gaia-domain-chat
pnpm add @ai-sdk/openai ai
This sets up:
- A TypeScript-based Next.js 14 project
- The new app router system
- Key dependencies:
- @ai-sdk/openai: Lets you stream from Gaia domains as if you’re using OpenAI
- ai: Handles streaming and token-based UI rendering
Folder Structure Overview
Here’s what your project will look like after some setup:
multi-domain-chat/
├─ app/
│ ├─ api/
│ │ └─ chat/
│ │ └─ route.ts # Proxies to Gaia Domain
│ ├─ models/
│ │ └─ route.ts # Lists available domains
│ ├─ layout.tsx # App shell (navbar, styles)
│ └─ page.tsx # Main chat UI
├─ components/
│ └─ ui/
│ ├─ chat-message.tsx # Chat bubble renderer
│ ├─ chat-sidebar.tsx # Sidebar logic
│ ├─ model-selector.tsx # Domain switcher
│ └─ model-setting-dialog.tsx # Gaia API modal
├─ lib/
│ └─ utils.ts # Helper functions
├─ public/ # Static assets
├─ globals.css # Tailwind styles
└─ .env.example # API key placeholder
Core Architecture Overview
This tutorial demonstrates how to implement a multi-domain chat system powered by Gaia Domains, enabling seamless interaction with specialized AI models hosted across different domains. The system is structured to maintain modularity, clarity, and adaptability, with clean separation between backend routing, frontend state management, and model integration.
Route Handler Implementation
In the app/api/chat/route.ts
file, the backend logic is responsible for routing messages to the appropriate Gaia domain based on the selected model.
One key utility is getModelProvider, which maps domain names to their corresponding Gaia API endpoints:
const getModelProvider = (modelId: string, apiKey: string) => {
const modelConfigs = {
metamask: "<https://metamask.gaia.domains/v1>",
base: "<https://base.gaia.domains/v1>",
polygon: "<https://polygon.gaia.domains/v1>",
scroll: "<https://scroll.gaia.domains/v1>",
zksync: "<https://zksync.gaia.domains/v1>",
}
const baseURL = modelConfigs[modelId as keyof typeof modelConfigs]
if (baseURL) {
const provider = createOpenAI({
baseURL,
apiKey,
})
return provider("llama")
}
// Fallback to OpenAI-compatible setup if model is not found
const openai = createOpenAI({ apiKey })
return openai("llama")
}
Highlights:
- Domain Specialization: Each domain corresponds to a tailored AI model for specific use cases (e.g. MetaMask interactions).
- Graceful Degradation: If a domain ID is unrecognized, the function falls back to a default configuration using the Gaia-compatible OpenAI wrapper.
The POST handler processes incoming messages and routes them to the selected domain:
export async function POST(req: Request) {
try {
const { messages, modelId } = await req.json()
// Streaming logic here...
} catch (error) {
console.error("Chat API error:", error)
return new Response(`Internal server error: ${error.message}`, { status: 500 })
}
}
This ensures the server continues to operate even when errors occur.
Model Management Route
The app/api/models/route.ts
file defines the available domains and their metadata. This allows the frontend to dynamically load and present domain options:
export async function GET() {
const models = [
{
id: "metamask",
name: "MetaMask AI",
provider: "Gaia",
description: "AI model specialized for MetaMask and Ethereum interactions",
baseURL: "<https://metamask.gaia.domains/v1>",
},
// Add more domains here...
]
return Response.json(models)
}
Key Points:
- Clear Metadata: Each model includes a unique id, a human-readable name, a description, and its domain URL.
- Dynamic Model Discovery: This API allows the frontend to retrieve and display the list of available models without hardcoding them.
Frontend Implementation
The frontend is located in app/page.tsx
, built using React with stateful components and Gaia chat integration.
1. State Initialization
State variables manage chat sessions, current selections, and message history:
const [chats, setChats] = useState<Chat[]>([])
const [currentChatId, setCurrentChatId] = useState<string | null>(null)
const [selectedModel, setSelectedModel] = useState("metamask")
2. Creating New Chats
When a user initiates a new conversation, a fresh chat object is added:
const createNewChat = () => {
const newChat: Chat = {
id: Date.now().toString(),
title: "New Chat",
createdAt: new Date(),
messages: [],
}
setChats((prev) => [newChat, ...prev])
setCurrentChatId(newChat.id)
setMessages([])
}
3. Domain-Aware Messaging
The chat input logic connects directly with Gaia domains through useChat:
const { messages, input, handleInputChange, handleSubmit, setMessages, isLoading } = useChat({
body: {
modelId: selectedModel,
},
// Streaming config here...
})
This hook takes care of message sending, response streaming, and UI state.
UI Component Structure
The frontend is broken into reusable, focused components.
Chat Sidebar
Handles chat navigation and creation:
interface ChatSidebarProps {
chats: Chat[]
currentChatId: string | null
onNewChat: () => void
onSelectChat: (chatId: string) => void
onDeleteChat: (chatId: string) => void
onOpenSettings: () => void
}
Model Selector
Presents the user with domain options:
interface ModelSelectorProps {
selectedModel: string
onModelChange: (modelId: string) => void
}
This promotes clear separation of concerns and keeps the interface flexible.
Technical Details
1. Error Management
The application logs and returns meaningful errors without crashing, making debugging easier and improving developer experience.
2. Local Storage for Chat Persistence
To retain chat history across reloads, chats are saved in localStorage:
useEffect(() => {
const savedChats = localStorage.getItem("chats")
if (savedChats) {
const parsedChats = JSON.parse(savedChats).map((chat: any) => ({
...chat,
createdAt: new Date(chat.createdAt),
}))
setChats(parsedChats)
}
}, [])
3. Dynamic Model Switching
Users can switch between Gaia domains effortlessly. Behind the scenes, the model ID sent to the backend determines which domain to use, without any change to the core chat logic.
If you want to take a look at the code, you can head over here: https://github.com/GaiaNet-AI/gaia-cookbook/tree/main/javascript-typescript/gaia-chat
Most chatbots funnel every question through a single LLM, but that one-size-fits-all approach crumbles when your app demands domain-specific knowledge, strict data separation, seamless horizontal scaling, and a built-in trust mechanism that rewards good actors and penalises bad ones. A multi-domain chat interface built on Gaia lets you sidestep those limitations: each domain houses its own specialised “brain” (think Ethereum wallet guru or zkEVM sage), guarantees hard isolation so proprietary data never leaks across contexts, and allows you to scale simply by spinning up more nodes—no refactor required. Because Gaia’s incentive layer pays reliable nodes and slashes malicious ones, you can offer a dropdown of ten distinct expertise areas with confidence that every answer is both authoritative and secure.
This multi-domain chat app is more than just a frontend for AI conversations — it’s a gateway into Gaia’s decentralized inference network. By combining the flexibility of Vercel Ai sdk with the plug-and-play nature of Gaia Domains, developers can easily build dynamic, real-time AI applications that route across multiple models without changing their architecture. Give it a try!
Happy Building 🚀