Building an AI Chatbot for Bangkok Clinics and Restaurants with Next.js and Claude
5 May 2026 · by Yunmin Shin
Why Build a Chatbot with Claude for a Bangkok Business?
A front desk at a Bangkok aesthetic clinic fields the same dozen questions all day: how much is a filler treatment, how long is downtime, do you take walk-ins, is there an English-speaking doctor. A restaurant discovery site gets the same pattern — is this place halal, is there parking, do they take reservations for groups of eight. None of that requires a human until the customer is ready to commit.
Claude's instruction-following and long context make it well suited to this: you can hand it a full treatment menu or restaurant listing as context and get answers that stay accurate to your actual pricing and policies, in whichever language the customer opens with. For a Bangkok business serving Thai locals, English-speaking expats, and — for clinics especially — Korean and Chinese medical tourists, that multilingual handling is the actual product, not a nice-to-have.
The pattern that works in production: the bot answers what it can from your real data, and hands off to a human on LINE the moment the conversation moves toward booking or payment. Claude answering FAQs at 2am and a staff member closing the actual sale on LINE the next morning is a division of labor that holds up.
How Do You Set Up the Claude API?
Install the Anthropic SDK:
npm install @anthropic-ai/sdk
Store the key server-side only:
ANTHROPIC_API_KEY=sk-ant-...
// lib/anthropic.ts
import Anthropic from "@anthropic-ai/sdk";
export const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
Never expose this key in client-side code. All calls must happen in Route Handlers, Server Actions, or server components — a chatbot widget embedded on a clinic's public site is exactly the kind of surface where a leaked key gets scraped and abused within hours.
How Do You Stream the Response?
Users on a mid-range Android phone over 4G notice the difference between a response that starts appearing after 400ms and one that arrives all at once after 4 seconds, even if the total time is similar. Use the Vercel AI SDK with the Anthropic provider:
npm install ai @ai-sdk/anthropic
// app/api/chat/route.ts
import { anthropic } from "@ai-sdk/anthropic";
import { streamText } from "ai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-5"),
system: CLINIC_SYSTEM_PROMPT,
messages,
});
return result.toDataStreamResponse();
}
On the client, the useChat hook from ai manages message state and streaming with almost no boilerplate.
How Do You Write a System Prompt That Actually Knows Your Business?
The system prompt is where a generic chatbot becomes your chatbot. For a Bangkok clinic, it needs specifics, not vibes:
const CLINIC_SYSTEM_PROMPT = `You are the assistant for [Clinic Name], an aesthetic
clinic in [district], Bangkok. You answer questions about treatments, pricing tiers,
and downtime using ONLY the information in the treatment menu provided below.
Rules:
- Respond in the language the user writes in (Thai, English, or Korean).
- Never diagnose, recommend a specific treatment for a medical condition, or give
a firm price for anything not listed — quote the listed price range and note
final pricing is confirmed by the doctor at consultation.
- If the user wants to book, is asking about a specific date, or wants to discuss
a photo they're sharing, say you're connecting them to staff and provide the
LINE OA link: https://line.me/R/ti/p/@clinichandle
- Do not discuss competitors or make delivery/downtime guarantees beyond the
ranges listed.
Treatment menu:
${treatmentMenuText}`;
That last rule — escalate on booking intent — is the one that matters most. Chatbots that try to complete bookings inline tend to produce awkward, error-prone conversations. LINE is where Thai customers actually expect to finalize a transaction with a real person.
How Do You Ground Answers in Real Treatment or Menu Data?
For a handful of services or a single menu, pasting the full text into the system prompt (as above) is simple and reliable — Claude's context window handles a few thousand words of treatment descriptions without difficulty. For a clinic running multiple locations with dozens of treatments and prices that change, pull the current menu from your database at request time instead of hardcoding it:
const treatments = await db.query("SELECT name, price_range, downtime FROM treatments WHERE active = true");
const menuText = treatments.map(t => `${t.name}: ${t.price_range}, downtime: ${t.downtime}`).join("\n");
This keeps the bot's answers synced with whatever the clinic manager last updated in the admin dashboard, rather than a stale prompt someone forgot to edit after a price change.
How Do You Maintain Conversation History and Control Cost?
Claude's API is stateless — you resend the full conversation on every request. useChat handles this client-side automatically. For persistence across sessions (a customer closes the tab and comes back), store messages in Supabase and reload the last 15–20 on return, which is enough context for a support conversation without inflating token cost.
Cost matters more than it first appears: a clinic's chatbot answering the same treatment questions dozens of times a day adds up. Capping conversation length, using a shorter model for simple FAQ deflection, and rate-limiting the endpoint per session all keep the bill predictable.
Ready to Build Something Fast?
Get a free quote. We reply within 24 hours.
Ready to build something fast and scalable?
Get a free project quote. We reply within 24 hours.
Get a Free Quote →