You launch a chatbot, ship the code, and three days later a stakeholder asks to swap "Hello" for "Hey there!" in the welcome message. Because that greeting is hard-coded, you push a commit, wait through a 3-minute build, redeploy, then test the change—only to repeat this when legal wants a new disclaimer.
Repeat this across a week and you've burned hours on copy edits instead of features.
The solution is separating concerns: keep conversational logic in your Next.js repo while moving all mutable text—and the bot's personality—to a headless CMS. Pairing the streaming power of the Vercel AI SDK with Strapi's real-time content APIs creates an architecture where non-developers update responses instantly and you focus on code that matters.
In Brief
- Separate your concerns by keeping conversational logic in Next.js while storing mutable content in a headless CMS to eliminate repetitive deployments for text changes.
- Empower non-developers to update bot responses and personality instantly through a content management interface without touching code.
- Leverage streaming capabilities of the Vercel AI SDK to deliver responsive, token-by-token chat experiences with minimal latency.
- Build a maintainable architecture that scales independently, letting you focus on core features instead of content updates.
Prerequisites
Before you wire up APIs or tweak prompts, confirm your environment checks out. Each requirement is familiar territory for experienced developers, but skipping any one of them will slow you down later.
- Node.js 18+ as the runtime for both Strapi and your local Vercel dev workflow. Install it system-wide so scripts run consistently across your development process.
- Next.js knowledge becomes essential here—you'll build the chat UI and server actions that communicate with the SDK.
- Vercel account setup for environment variables, edge functions, and zero-config deploys. After signing in, review the AI SDK docs to understand the integration patterns you'll implement.
- Strapi 5 familiarity pays off when you model content and expose it via REST or GraphQL—the integration guide walks through the complete setup process.
- Basic AI/LLM concepts like prompts, tokens, and provider APIs. The SDK handles the complexity, but reading the introduction helps you craft effective system messages that deliver the responses your users expect.
Setting Up Your Project
Create a TypeScript-ready Next.js app and install the SDK:
1npx create-next-app@latest ai-chatbot --typescript
2cd ai-chatbot
3npm install ai # Vercel AI SDKThe generator outputs:
1✔ Project initialized Created ai-chatbot/
2✔ Typescript enabled tsconfig.json generated
3✔ Next steps cd ai-chatbot && npm run devThe SDK's server helpers and React hooks are exposed from the ai package. Create a .env.local file at your project root:
1OPENAI_API_KEY=pk_live_***
2STRAPI_URL=http://localhost:1337Vercel automatically injects these variables during deployment.
Your folder structure should look like this:
1ai-chatbot/
2 ├─ pages/
3 │ └─ api/
4 │ └─ chat.ts # your streaming route
5 ├─ components/
6 │ └─ Chat.tsx # React UI
7 ├─ .env.local
8 ├─ tsconfig.json
9 └─ package.jsonTypeScript catches mismatched message shapes and missing environment variables before runtime. You now have a live-reload dev server (npm run dev), typed endpoints, and a flexible foundation ready for Strapi content integration. The SDK's pluggable model layer allows you to swap AI providers as needed.
Integrating the Vercel AI SDK
The package includes server-side helpers for Next.js API routes and client-side hooks that handle state management automatically. Create a chat API route that streams model output in real time:
1// app/api/chat/route.ts
2import { streamText } from "ai";
3import { openai } from "@ai-sdk/openai";
4import type { Message } from "ai";
5
6export async function POST(req: Request) {
7 const { messages } = (await req.json()) as { messages: Message[] };
8
9 // streamText returns a ReadableStream so the client sees tokens instantly
10 const response = await streamText({
11 model: openai("gpt-4o-mini"), // swap to "anthropic:claude" or any other provider
12 messages,
13 maxTokens: 800,
14 });
15
16 // Next.js converts the stream to a proper response object
17 return response.toDataStreamResponse();
18}streamText handles chunking and HTTP headers automatically. The browser receives tokens as the model produces them, reducing perceived latency through streaming responses.
The useChat hook manages input, message history, loading states, and errors:
1// components/Chat.tsx
2"use client";
3
4import { useChat } from "ai/react";
5
6export default function Chat() {
7 const {
8 messages,
9 input,
10 handleInputChange,
11 handleSubmit,
12 isLoading,
13 error,
14 } = useChat({ api: "/api/chat" });
15
16 return (
17 <form onSubmit={handleSubmit} className="space-y-4">
18 <ul>
19 {messages.map((m) => (
20 <li key={m.id} className={m.role === "user" ? "text-right" : ""}>
21 {m.content}
22 </li>
23 ))}
24 </ul>
25
26 <input
27 value={input}
28 onChange={handleInputChange}
29 placeholder="Ask me anything…"
30 className="w-full border p-2"
31 />
32
33 {isLoading && <p className="text-sm">Generating…</p>}
34 {error && <p className="text-sm text-red-600">{error.message}</p>}
35 </form>
36 );
37}useChat posts to your route, appends streamed tokens, and handles transient failures. You can extend it with callbacks like onFinish for analytics or onError for custom logging.
Provider selection uses string identifiers in the format 'provider/model' (e.g., 'openai/gpt-5'), so switching models requires only an environment variable change. No code modifications needed.
To test your integration locally, set your OPENAI_API_KEY in .env.local and run npm run dev. Navigate to /chat and type a message—you should see words appear token by token. If nothing renders, check the Network tab. Most issues stem from missing API keys or CORS configuration.
Deploy to production with vercel --prod. The platform automatically injects environment variables and scales your serverless function to handle traffic spikes. When the model encounters errors (rate limits, context length exceeded), the hook exposes them via the error property. This lets you display graceful fallbacks instead of silent failures.
With fewer than fifty lines of code, you have a functional, provider-agnostic streaming foundation.
Connecting AI Chatbot to Strapi
Your assistant needs dynamic content that updates without redeployment. Strapi provides an API layer for the knowledge your content team maintains, letting you query current information in milliseconds.
Start by spinning up a local Strapi instance:
1npx create-strapi-app@latest acme-chatbot-cms --quickstartThe --quickstart flag uses SQLite for local development. Switch to PostgreSQL for production to handle better concurrency and backups. The Strapi × Next.js integration guide covers database migration in detail.
Once the Admin Panel opens, create a Collection Type called FAQ with four fields:
question(Text)answer(Rich Text)category(Enumeration)tags(UID or Repeatable Component)
This schema optimizes for semantic matching—question and answer feed embeddings while category and tags filter results. Strapi auto-saves changes, so content editors iterate without touching Git.
Enable API access by navigating to Settings → Roles & Permissions and checking find and findOne on faq for the Public role. Keep write actions disabled to protect your data while exposing read-only endpoints.
Test the autogenerated REST endpoint:
1curl https://your-strapi-url.com/api/faqs?populate=*&filters[category][$eq]=generalExpect this response structure in Strapi 5:
1{
2 "data": [
3 {
4 "id": 12,
5 "documentId": "abc123xyz",
6 "question": "How do I reset my password?",
7 "answer": "Click \"Forgot password\" on the login screen and follow the email instructions.",
8 "category": "account",
9 "tags": ["security", "account"],
10 "createdAt": "2024-03-06T13:42:05.098Z",
11 "updatedAt": "2024-03-06T13:42:05.098Z",
12 "publishedAt": "2024-03-06T13:42:05.103Z",
13 "locale": "en"
14 }
15 ],
16 "meta": {
17 "pagination": {
18 "page": 1,
19 "pageSize": 25,
20 "pageCount": 1,
21 "total": 42
22 }
23 }
24}To whitelist your Vercel app origin for CORS in Strapi, use the following configuration in ./config/middlewares.js and remove 'enabled: true':
1module.exports = [
2 'strapi::logger',
3 'strapi::errors',
4 'strapi::security',
5 {
6 name: 'strapi::cors',
7 config: {
8 origin: ['https://your-vercel-app.vercel.app'],
9 methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
10 headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],
11 },
12 },
13 'strapi::poweredBy',
14 'strapi::query',
15 'strapi::body',
16 'strapi::session',
17 'strapi::favicon',
18 'strapi::public',
19];Create a utility function that fetches content and handles empty results:
1// lib/getFaq.ts
2export async function getFaq(query: string) {
3 const url = new URL('/api/faqs', process.env.STRAPI_URL);
4 url.searchParams.append('filters[question][$containsi]', query);
5 url.searchParams.append('populate', '*');
6
7 const res = await fetch(url.href, { headers: { Authorization: `Bearer ${process.env.STRAPI_TOKEN}` } });
8
9 if (!res.ok) throw new Error(`Strapi error: ${res.status}`);
10 const { data } = await res.json();
11 return data.length ? data[0].attributes.answer : null;
12}Integrate the retrieved answer into your conversation workflow:
1// app/api/chat/route.ts
2import { streamText } from 'ai';
3import { getFaq } from '@/lib/getFaq';
4
5export async function POST(req: Request) {
6 const { messages } = await req.json();
7 const question = messages.at(-1)?.content ?? '';
8
9 const canned = await getFaq(question);
10 const system = canned
11 ? `[Use the following answer when relevant:\n\n$](https://strapi.io/blog/eight-headless-cms-use-cases){canned}`
12 : 'Answer conversationally.';
13
14 return streamText({
15 model: 'openai:gpt-4o-mini',
16 messages: [{ role: 'system', content: system }, ...messages],
17 }).toDataStreamResponse();
18}Your assistant now pulls live answers from Strapi. Content editors update copy through the CMS while you focus on features instead of content deployments.
Supercharging Your AI Chatbot with Strapi AI
Strapi AI introduces powerful capabilities that can transform your chatbot project from basic FAQ handling to an intelligent content delivery system. Let's explore how to integrate these advanced features.
Generate Content Types with Natural Language
Instead of manually defining your FAQ structure, use Strapi AI to generate it through conversation:
- Ensure you have Strapi version 5.26 or higher with a valid Growth plan license
- Navigate to the Content-Type Builder in the Admin Panel
- Click the Strapi AI icon in the bottom-right corner
- Use this prompt to generate a complete chatbot content structure:
1Create content types for a customer service chatbot with FAQ categories, response templates, conversation flows, and feedback trackingStrapi AI will generate a comprehensive schema including:
- FAQ collections with appropriate fields
- Response templates with variable placeholders
- Conversation flow models with conditional paths
- User feedback tracking
This approach creates a more sophisticated content structure than our basic example, with proper relationships and optimized field types—all without writing a single line of schema code.
Import from Existing Project
If you've already built a frontend with hardcoded responses, you can reverse-engineer your content model:
- Export your Next.js project as a ZIP file
- In Strapi AI, click "Import from computer" and upload the ZIP
- The AI will analyze your code to identify:
- Hardcoded messages and responses
- UI components displaying content
- Data structures and types
- Review and confirm the generated content types
This approach bridges the gap between your existing frontend and a proper CMS-backed solution, accelerating the transition to dynamic content.
Enhance Your FAQ Collection
Once your base structure is created, use Strapi AI to improve it with follow-up prompts:
1Add a markdown rich text field called 'additionalContext' to the FAQ1Create an SEO component with meta fields and add it to all content types1Organize the response templates with a blocks dynamic zone for flexible contentThese improvements make your content structure more robust and flexible, supporting rich formatting, SEO optimization, and modular content building—all critical for delivering high-quality chatbot responses.
Building Intelligence Into Your Chatbot
You already have a working chat endpoint, but real value comes when the bot feels genuinely informed and remembers what's happening in the conversation. Let's add that intelligence layer by combining Strapi-managed content with thoughtful prompt controls.
Context-Aware Content Retrieval
Users rarely phrase questions exactly as they appear in your knowledge base. Your first task is finding the right Strapi entry even when wording differs. Start simple with text matching—query Strapi's REST endpoint using a case-insensitive contains filter—then evolve to semantic search when you need deeper understanding.
1// lib/semanticSearch.ts
2import { embed, similaritySearch } from "@pinecone-database/client";
3import { fetchArticles } from "./strapi"; // wraps Strapi REST call
4
5export async function semanticSearch(query: string) {
6 // 1. Embed the incoming query
7 const queryVector = await embed(query);
8
9 // 2. Retrieve the top 3 semantically similar pieces from Pinecone
10 const matches = await similaritySearch(queryVector, { topK: 3 });
11
12 // 3. Fallback to direct Strapi search if no vector match
13 if (matches.length === 0) {
14 return fetchArticles({ filters: { question: { $containsi: query } } });
15 }
16 return matches.map((m) => m.metadata.text);
17}Once you have candidate snippets, inject them into a system prompt:
1const system = `
2You are a helpful assistant. Base your answer strictly on the context below.
3Context:
4${context.join("\n\n")}
5`;Watch your token limits. Cap the concatenated context at 2,000 tokens and let the LLM decide what matters. For performance, cache successful Strapi lookups in memory or Redis. Vercel Edge cache works well for read-heavy FAQ data, giving you microsecond retrievals close to users.
If no content surfaces, send a concise apology and route the query directly to the model—never return empty strings that could confuse the prompt. When multiple pieces match, rank by semantic score, then by recency. This surfaces the most current answer without extra manual curation.
Query Understanding and Context Management
Retrieving good data solves only half the problem; your assistant also needs to carry on coherent conversations. The useChat hook keeps message state on the client, but you decide how much history to forward to the model. A sliding window of the last six user/assistant pairs balances relevance and cost. Older messages get truncated:
1function trimHistory(messages: Message[], limit = 12) {
2 return messages.slice(-limit);
3}System prompts deserve equal care. A minimal template includes role, tone, and capabilities:
1const baseSystem = `
2You are StrapiBot, an expert on our headless CMS.
3Answer in under 120 words and cite internal IDs when relevant.
4`;Before each request, merge baseSystem, the retrieved Strapi context, and the trimmed history. Intent classification decides whether you even need the database lookup. Lightweight heuristics work: if the user message contains "how", "what", or "why" plus a Strapi keyword, fetch content; otherwise, let the model improvise. Refine this with embeddings or an external classifier later.
Edge cases matter. If a follow-up question references "that plugin we discussed," pull the last referenced plugin name from history and include it explicitly in the next system prompt. For ambiguous queries, ask clarifying questions rather than guessing—misinformation erodes trust faster than a short delay.
Stream responses (streamText) so users see progress instantly. The SDK's default error handling retries transient network hiccups, but you should still catch 4xx or 5xx responses from Strapi and surface polite, actionable messages.
By layering smart retrieval, disciplined prompt construction, and careful state management, you transform a basic chat interface into a context-aware assistant that feels tailored to every user while staying maintainable in both code and content.
Testing and Deployment
Before you push to production, test like your reputation depends on it. A tight feedback loop catches logic gaps, API failures, and prompt-injection surprises before real users encounter them.
Run through this checklist every time you iterate:
- Edge-case phrasing: slang, typos, multi-sentence prompts
- Follow-up questions that rely on previous context
- Queries that must map to Strapi content—and ones that should bypass it
- Failure conditions: Strapi offline, bad API key, model timeout
When something breaks, start with local logs, then reproduce in a Vercel preview deployment. Vercel surfaces request traces and streamed responses in its dashboard, so you can inspect every token the model returns. If the issue starts in your content layer, Strapi's detailed 4xx/5xx responses make the root cause obvious.
Deploying is straightforward. Commit to GitHub, run vercel once to link the project, and every subsequent push spins up a preview URL. Promote a tested commit to production from the Vercel UI or via protected branches.
Environment variables (OPENAI_API_KEY, STRAPI_URL, etc.) live in the Vercel dashboard. Missing vars cause most "works on my machine" bugs, so validate them in both Preview and Production scopes.
Next, lock down access. Configure Strapi to only accept traffic from your frontend origin:
1// config/middleware.js
2module.exports = [
3 {
4 name: 'strapi::cors',
5 config: {
6 origin: ['https://your-vercel-app.vercel.app'],
7 methods: ['GET', 'POST'],
8 headers: ['Content-Type', 'Authorization'],
9 credentials: true,
10 },
11 },
12];Finally, protect your wallet and uptime with rate limiting. For production use, implement a distributed rate limiter—such as one backed by Redis or a dedicated service—rather than a simple in-memory Map, to prevent abusive clients from hammering model APIs reliably across all instances.
Taking It Further with Strapi AI
The Strapi AI Media Library can supercharge your chatbot with intelligent content processing. Here's how to incorporate these premium capabilities:
Automated Media Enhancement
For chatbots that handle image uploads or provide visual responses:
- Configure Strapi AI Media Library to automatically:
- Generate alt-text for all images
- Create descriptive captions
- Apply intelligent tagging
- Use the tagged media in your chat responses for more contextually relevant answers:
1async function getRelevantMedia(query) {
2 const url = new URL('/api/media-library', process.env.STRAPI_URL);
3 url.searchParams.append('filters[tags][$containsi]', query);
4
5 const res = await fetch(url.href);
6 return res.json();
7}
8
9// In your chat route
10const relevantMedia = await getRelevantMedia(question);
11if (relevantMedia.length) {
12 system += `\nRefer to this image if relevant: ${relevantMedia[0].url}`;
13}This allows your chatbot to provide visual examples alongside text responses, creating a richer user experience with minimal development effort.
Future-Proofing Your Chatbot
As Strapi AI evolves, your chatbot can take advantage of upcoming features:
- AI Translation: When internationalization support releases, your chatbot will automatically respond in the user's language without code changes.
- Intelligent Tagging: Leverage automatic content categorization to improve retrieval accuracy.
- AI Content Generation: Enable dynamic response creation based on user queries that don't match existing content.
To prepare for these capabilities:
1// Extensible retrieval function that can incorporate new AI features
2async function getContent(query, options = {}) {
3 const { language = 'en', useAIGeneration = false } = options;
4 const url = new URL('/api/content', process.env.STRAPI_URL);
5
6 // Standard filters
7 url.searchParams.append('filters[question][$containsi]', query);
8 url.searchParams.append('locale', language);
9
10 // Flag for AI-enhanced responses when available
11 if (useAIGeneration) {
12 url.searchParams.append('ai-generate', 'true');
13 }
14
15 const res = await fetch(url.href);
16 return res.json();
17}This future-proof approach ensures your chatbot can easily adopt new Strapi AI capabilities as they become available.
Ready to Ship Your Intelligent Chatbot
You've solved the original problem: no more fragile, hard-coded responses that break every time content changes. Your assistant now updates itself the moment someone edits content in Strapi. Deployments no longer gatekeep simple copy changes, and stakeholders can safely adjust tone or FAQs without touching your codebase.
Streaming responses keep conversations fast, while Strapi's API-driven architecture maintains clean separation of concerns. Both services scale independently on their own infrastructure.
What's next? Explore the Retrieval-Augmented template on Vercel's marketplace for deeper knowledge grounding. Add multilingual support with Strapi's i18n plugin. Check out the community resources around Strapi + Vercel integration for advanced patterns.
Your foundation handles the basics—now you can iterate on user experience, measure conversation quality, and build more sophisticated AI applications.