Back to blog
GCPVertex AINext.jsAITutorial

Build a Simple AI Chat Interface with GCP Vertex AI and Next.js

Pawan Sargar June 20, 2026 6 min read

Learn how to connect Google Cloud Vertex AI to a Next.js app and build a simple, working chat interface step by step — no fluff, just code.

Build a Simple AI Chat Interface with GCP Vertex AI and Next.js

If you've been curious about adding AI chat to your Next.js app, Google Cloud's Vertex AI is a solid option. It gives you access to Gemini models without dealing with third-party APIs. In this guide, I'll show you how to wire it up and build a working chat UI — kept simple on purpose.

What We're Building

A Next.js app with:

  • An API route that calls Vertex AI (Gemini model)
  • A simple chat UI with message history
  • No extra libraries or heavy dependencies

Prerequisites

Before starting, you'll need:

  • A Google Cloud account with a project created
  • Vertex AI API enabled in that project
  • Node.js 18+ and a working Next.js 14+ app
  • gcloud CLI installed and authenticated locally

If you don't have a Next.js app yet, spin one up:

npx create-next-app@latest my-chat-app
cd my-chat-app

Step 1: Enable Vertex AI and Set Up Authentication

Go to Google Cloud Console, open your project, and enable the Vertex AI API.

For local development, authenticate with:

gcloud auth application-default login

This creates a credentials file that the Google SDK picks up automatically. No need to manually manage keys during development.

For production (Vercel, Cloud Run, etc.), you'll use a service account. Download the JSON key, and we'll reference it shortly.


Step 2: Install the Google Cloud AI Platform Package

npm install @google-cloud/vertexai

That's the only dependency needed. It's the official Google library for Vertex AI.


Step 3: Set Up Environment Variables

Create a .env.local file in your project root:

GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=us-central1

For production, if using a service account key file:

GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

Step 4: Create the API Route

Create a new file at app/api/chat/route.ts:

import { VertexAI } from '@google-cloud/vertexai';
import { NextRequest, NextResponse } from 'next/server';
 
const vertexAI = new VertexAI({
  project: process.env.GOOGLE_CLOUD_PROJECT!,
  location: process.env.GOOGLE_CLOUD_LOCATION || 'us-central1',
});
 
const model = vertexAI.getGenerativeModel({
  model: 'gemini-1.5-flash',
});
 
export async function POST(req: NextRequest) {
  try {
    const { message } = await req.json();
 
    if (!message) {
      return NextResponse.json({ error: 'Message is required' }, { status: 400 });
    }
 
    const result = await model.generateContent(message);
    const response = result.response;
    const text = response.candidates?.[0]?.content?.parts?.[0]?.text ?? 'No response';
 
    return NextResponse.json({ reply: text });
  } catch (error) {
    console.error('Vertex AI error:', error);
    return NextResponse.json({ error: 'Failed to get response' }, { status: 500 });
  }
}

We're using gemini-1.5-flash here because it's fast and cheap for a simple chat use case. You can swap it for gemini-1.5-pro if you need heavier reasoning.


Step 5: Build the Chat UI

Create app/chat/page.tsx:

'use client';
 
import { useState } from 'react';
 
type Message = {
  role: 'user' | 'assistant';
  content: string;
};
 
export default function ChatPage() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [loading, setLoading] = useState(false);
 
  async function sendMessage() {
    if (!input.trim()) return;
 
    const userMessage: Message = { role: 'user', content: input };
    setMessages((prev) => [...prev, userMessage]);
    setInput('');
    setLoading(true);
 
    try {
      const res = await fetch('/api/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: input }),
      });
 
      const data = await res.json();
      const assistantMessage: Message = {
        role: 'assistant',
        content: data.reply || data.error,
      };
      setMessages((prev) => [...prev, assistantMessage]);
    } catch {
      setMessages((prev) => [
        ...prev,
        { role: 'assistant', content: 'Something went wrong. Please try again.' },
      ]);
    } finally {
      setLoading(false);
    }
  }
 
  function handleKeyDown(e: React.KeyboardEvent) {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  }
 
  return (
    <div style={{ maxWidth: 600, margin: '40px auto', padding: '0 16px' }}>
      <h1>AI Chat</h1>
 
      <div
        style={{
          border: '1px solid #ddd',
          borderRadius: 8,
          padding: 16,
          height: 400,
          overflowY: 'auto',
          marginBottom: 16,
          display: 'flex',
          flexDirection: 'column',
          gap: 12,
        }}
      >
        {messages.length === 0 && (
          <p style={{ color: '#999', textAlign: 'center', marginTop: 'auto' }}>
            Ask me anything...
          </p>
        )}
        {messages.map((msg, i) => (
          <div
            key={i}
            style={{
              alignSelf: msg.role === 'user' ? 'flex-end' : 'flex-start',
              background: msg.role === 'user' ? '#0070f3' : '#f0f0f0',
              color: msg.role === 'user' ? '#fff' : '#000',
              padding: '8px 14px',
              borderRadius: 16,
              maxWidth: '80%',
            }}
          >
            {msg.content}
          </div>
        ))}
        {loading && (
          <div style={{ alignSelf: 'flex-start', color: '#999' }}>Thinking...</div>
        )}
      </div>
 
      <div style={{ display: 'flex', gap: 8 }}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={handleKeyDown}
          placeholder="Type your message..."
          style={{
            flex: 1,
            padding: '10px 14px',
            borderRadius: 8,
            border: '1px solid #ddd',
            fontSize: 16,
          }}
        />
        <button
          onClick={sendMessage}
          disabled={loading}
          style={{
            padding: '10px 20px',
            borderRadius: 8,
            background: '#0070f3',
            color: '#fff',
            border: 'none',
            cursor: 'pointer',
            fontSize: 16,
          }}
        >
          Send
        </button>
      </div>
    </div>
  );
}

The UI is intentionally plain. No CSS framework needed to get this running.


Step 6: Test It Locally

npm run dev

Visit http://localhost:3000/chat and send a message. If your gcloud auth is set up correctly, you should get a response within a second or two.


A Few Things to Keep in Mind

Cost: Vertex AI charges per token. gemini-1.5-flash is much cheaper than gemini-1.5-pro. Keep an eye on your GCP billing dashboard.

Rate limits: There are quotas per minute per project. For production apps, add error handling and maybe a rate limit on your API route using something like Upstash Redis.

Chat history: The current setup sends only the latest message. To support multi-turn conversations, you'd send the full message history to Vertex AI using its chat session API — worth exploring once the basics work.

Security: Never expose your service account key or project ID on the client side. The API route keeps everything server-side, which is correct.


Deploying to Production

If you're deploying to Vercel:

  1. Go to your project settings → Environment Variables
  2. Add GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION
  3. For authentication, create a service account in GCP with the Vertex AI User role, download the JSON key, and set GOOGLE_APPLICATION_CREDENTIALS pointing to its path — or paste the JSON content as an env variable and load it in code

For Cloud Run or GKE, workload identity is cleaner than key files. But that's a topic for another post.


Wrapping Up

That's all it takes to get a working AI chat interface using Vertex AI and Next.js. The setup is clean, the code is minimal, and you have full control over the model and API.

From here, you can extend it — add streaming responses, save chat history to a database, or build a more polished UI on top of this foundation.

If you run into issues with authentication, the Vertex AI quickstart docs are genuinely helpful. Also check out our post on building scalable web apps if you want to think about how to structure this as your app grows.

Have questions? Drop a comment or reach out. Happy building.

Written by
Pawan Sargar
Founder & lead developer at Waystoweb Technologies.