Lompat ke konten utama
vourdev
Kembali ke Blog

Tutorial

Vercel AI SDK 4.0: Bikin Chatbot Streaming di Next.js Cuma 30 Baris Kode

Panduan praktis membangun AI chatbot streaming di Next.js App Router menggunakan Vercel AI SDK 4.0, hook useChat, dan OpenAI/Anthropic provider.

Gambar Cover Vercel AI SDK 4.0: Bikin Chatbot Streaming di Next.js Cuma 30 Baris Kode
Ditulis olehvourdev10 menit baca

Dulu, bikin fitur chatbot streaming ala ChatGPT itu nightmare. Kamu harus set up Server-Sent Events (SSE) atau WebSocket manual, ngurusin chunk parsing dari OpenAI API, kelola array messages di React state supaya nggak re-render secara brutal, dan nanganin edge cases seperti koneksi terputus di tengah jalan. Kalau kodenya nggak rapi, React state kamu langsung berantakan dan performa aplikasi meluncur turun.

Di Vercel AI SDK 4.0, semua kompleksitas itu dibabat habis. Vercel menyajikan abstraksi yang matang tanpa mengorbankan fleksibilitas. Dengan kombinasi App Router Next.js dan AI SDK 4.0, kamu cuma butuh kurang dari 30 baris kode untuk membangun UI chatbot production-ready yang mendukung real-time streaming, multi-provider (OpenAI, Anthropic, Google Gemini), serta error handling yang tangguh.

Artikel ini akan membedah arsitektur Vercel AI SDK 4.0 dan membimbing kamu step-by-step membuat chatbot streaming berkinerja tinggi.

Arsitektur Vercel AI SDK 4.0 & Kenapa Harus Upgrade

Vercel AI SDK 4.0 bukan sekadar update minor. Ada pergeseran paradigma utama dibanding versi 2.x atau 3.x awal. Dulu SDK ini sangat terikat erat dengan format OpenAI dan Next.js Server Actions. Sekarang, SDK dipecah menjadi dua layer independen:

  1. AI SDK Core (`ai`): Menyediakan API universal untuk memanggil LLM (Large Language Models), generate text, generate object (structured data), dan stream response. Layer ini murni JavaScript runtime dan bisa berjalan di Node.js, Edge Runtime, Cloudflare Workers, atau Bun.
  2. AI SDK UI (`ai/react`): Framework-agnostic hooks (seperti useChat dan useCompletion) untuk mengelola state UI, input form, optimistic updates, dan konsumsi stream data dari backend secara otomatis.

Keunggulan utama Vercel AI SDK 4.0 terletak pada Unified Provider Interface. Kode backend kamu tidak lagi bergantung pada SDK spesifik milik vendor. Ganti model dari gpt-4o ke claude-3-5-sonnet cukup dengan mengubah satu baris impor provider, tanpa menyentuh logika streaming atau UI.


Setup Project & Dependensi

Mari kita mulai dari nol. Buat proyek Next.js baru dengan App Router dan TypeScript, lalu install package yang dibutuhkan.

bash
npx create-next-app@latest my-ai-chatbot --typescript --tailwind --eslint --app
cd my-ai-chatbot

# Install Vercel AI SDK versi 4.0+ dan Provider Adapter
npm install ai @ai-sdk/openai @ai-sdk/anthropic

Jangan lupa untuk menyiapkan API Key kamu di environment variable .env.local:

env
OPENAI_API_KEY=sk-proj-your-openai-api-key-here
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here

@ai-sdk/openai dan @ai-sdk/anthropic secara otomatis membaca process.env.OPENAI_API_KEY dan process.env.ANTHROPIC_API_KEY dari server environment. Kunci API kamu aman di sisi server dan tidak akan pernah bocor ke client-side bundle.


Menjalankan Backend Route Handler

Langkah berikutnya adalah membuat Route Handler di Next.js App Router. Buat file baru di app/api/chat/route.ts.

Di file ini, kita mengimpor fungsi streamText dari package ai dan instans model dari @ai-sdk/openai.

typescript
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

// Mengatur runtime ke edge untuk kineja latency terendah (opsional)
export const runtime = 'edge';

export async function POST(req: Request) {
  try {
    const { messages } = await req.json();

    // Memanggil LLM dan membuat response stream
    const result = streamText({
      model: openai('gpt-4o-mini'),
      system: 'Kamu adalah asisten teknis senior yang ahli, ramah, dan selalu memberikan jawaban yang lugas serta praktis.',
      messages,
    });

    // Mengembalikan Data Stream Response yang dipahami oleh UI SDK
    return result.toDataStreamResponse();
  } catch (error) {
    console.error('Error on POST /api/chat:', error);
    return new Response(
      JSON.stringify({ error: 'Terjadi kesalahan saat memproses permintaan AI.' }),
      { status: 500, headers: { 'Content-Type': 'application/json' } }
    );
  }
}

Detail Implementasi Backend:

  • `streamText`: Fungsi utama dari AI SDK Core untuk menginisiasi koneksi streaming ke provider LLM.
  • `model: openai('gpt-4o-mini')`: Menggunakan model cost-effective milik OpenAI. Jika ingin ganti ke GPT-4o penuh, cukup ganti string menjadi 'gpt-4o'.
  • `system`: Prompt instruksi awal untuk mengatur persona dan batasan perilaku model AI.
  • `result.toDataStreamResponse()`: Fungsi ajaib dari Vercel AI SDK 4.0. Fungsi ini mengonversi stream dari LLM ke format standar SSE (Server-Sent Events) khusus Vercel AI Data Stream Protocol, yang secara otomatis dipahami oleh hook useChat di frontend.

Menjalankan Client UI dengan useChat Hook

Sekarang masuk ke bagian ajaib di frontend. Kita buat komponen chat interface lengkap di app/page.tsx.

Hook useChat dari ai/react akan menangani seluruh state pesan (messages), nilai input form (input), status loading (isLoading), submission handler (handleSubmit), serta auto-appending chunk text yang masuk secara real-time.

tsx
'use client';

import { useChat } from 'ai/react';

export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit, isLoading, error, reload } = useChat({
    api: '/api/chat',
  });

  return (
    <main className="flex flex-col max-w-2xl mx-auto h-screen p-4">
      <header className="py-4 border-b">
        <h1 className="text-xl font-bold">Vour Chatbot Assistant</h1>
        <p className="text-sm text-gray-500">Powered by Vercel AI SDK 4.0 & Next.js</p>
      </header>

      {/* Container Chat Messages */}
      <div className="flex-1 overflow-y-auto py-4 space-y-4">
        {messages.length === 0 && (
          <p className="text-center text-gray-400 mt-8">Belum ada percakapan. Ketik sesuatu untuk memulai!</p>
        )}

        {messages.map((m) => (
          <div
            key={m.id}
            className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}
          >
            <div
              className={`max-w-[80%] rounded-lg p-3 ${
                m.role === 'user'
                  ? 'bg-blue-600 text-white'
                  : 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100'
              }`}
            >
              <span className="font-semibold text-xs block mb-1">
                {m.role === 'user' ? 'You' : 'AI Assistant'}
              </span>
              <p className="whitespace-pre-wrap text-sm">{m.content}</p>
            </div>
          </div>
        ))}

        {/* Loading Indicator */}
        {isLoading && (
          <div className="flex justify-start">
            <div className="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 animate-pulse text-sm text-gray-500">
              Sedang mengetik...
            </div>
          </div>
        )}

        {/* Error Handler State */}
        {error && (
          <div className="p-3 bg-red-50 text-red-600 rounded-lg text-sm flex justify-between items-center">
            <span>Terjadi kesalahan saat memuat respon.</span>
            <button
              onClick={() => reload()}
              className="underline font-medium hover:text-red-800 ml-2"
            >
              Coba lagi
            </button>
          </div>
        )}
      </div>

      {/* Form Input Message */}
      <form onSubmit={handleSubmit} className="flex gap-2 pt-2 border-t">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Tanyakan sesuatu tentang Next.js..."
          className="flex-1 border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-900 dark:border-gray-700"
          disabled={isLoading}
        />
        <button
          type="submit"
          disabled={isLoading || !input.trim()}
          className="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium disabled:opacity-50 hover:bg-blue-700 transition"
        >
          Kirim
        </button>
      </form>
    </main>
  );
}

Detail Implementasi Frontend:

  1. `useChat()`: Hook ini otomatis menargetkan /api/chat secara default (bisa kita passing opsi { api: '/api/custom-route' }). Hook mengelola array messages yang selalu di-update begitu stream chunk baru tiba dari backend.
  2. `handleInputChange` & `handleSubmit`: Mengikat input HTML secara deklaratif. Saat form di-submit, handleSubmit otomatis mengosongkan input, menambahkan pesan user ke state secara optimistic, lalu mengirim seluruh riwayat percakapan ke backend via POST.
  3. `reload()`: Memberikan kemampuan untuk retry jika ada error koneksi saat streaming.

Total kode pada file React di atas hanya berkisar 60-70 baris dengan styling Tailwind Tailwind CSS lengkap. Jika tanpa styling, komponen dasar murni di bawah 30 baris kode!


Handling Edge Cases: Multi-Provider & Custom Fallback Error

Aplikasi production-ready tidak boleh hancur begitu provider utama seperti OpenAI sedang mengalami kecemasan kapasitas (rate limit atau outage). Salah satu kekuatan terbesar Vercel AI SDK 4.0 adalah kemudahan dalam mengimplementasikan fallback mechanism atau dynamic provider switching.

Mari kita tingkatkan Route Handler app/api/chat/route.ts agar secara otomatis beralih (fallback) ke Anthropic Claude 3.5 Sonnet jika OpenAI mengalami kegagalan.

typescript
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
import { streamText } from 'ai';

export const runtime = 'edge';

export async function POST(req: Request) {
  const { messages } = await req.json();

  // Validasi payload pesan sederhana
  if (!messages || !Array.isArray(messages)) {
    return new Response(JSON.stringify({ error: 'Payload messages tidak valid.' }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  try {
    // Upaya 1: Panggil OpenAI API (Primary Provider)
    const result = streamText({
      model: openai('gpt-4o-mini'),
      system: 'Kamu adalah AI engineer berpengalaman yang menjawab secara singkat dan tepat.',
      messages,
      maxTokens: 1000,
    });

    return result.toDataStreamResponse();
  } catch (primaryError) {
    console.warn('OpenAI gagal atau Rate Limited, beralih ke Anthropic Claude...', primaryError);

    try {
      // Upaya 2: Fallback ke Anthropic API (Secondary Provider)
      const fallbackResult = streamText({
        model: anthropic('claude-3-5-sonnet-20240620'),
        system: 'Kamu adalah AI engineer berpengalaman yang menjawab secara singkat dan tepat.',
        messages,
        maxTokens: 1000,
      });

      return fallbackResult.toDataStreamResponse();
    } catch (fallbackError) {
      console.error('Kedua provider (OpenAI & Anthropic) gagal dipanggil:', fallbackError);
      return new Response(
        JSON.stringify({ error: 'Layanan AI sedang tidak dapat diakses saat ini. Silakan coba lagi nanti.' }),
        { status: 503, headers: { 'Content-Type': 'application/json' } }
      );
    }
  }
}
Tips Senior Dev: Selalu atur parameter maxTokens untuk mencegah runaway request dari pengguna malisius yang memicu generasi teks sangat panjang sehingga membengkakkan tagihan API kamu.

Tips & Best Practices Production-Ready

Sebelum kamu men-deploy aplikasi ini ke Vercel atau infrastructure pilihanmu, perhatikan beberapa aturan main penting ini:

1. Jangan Pernah Panggil API Key dari Client-Side

Jangan pernah tergiur membuat tombol atau halaman setting di client tempat pengguna/dev memasukkan apiKey langsung ke dalam instance createOpenAI({ apiKey }) di komponen browser ('use client'). Semua interaksi AI wajib melalui Route Handler server-side untuk menyembunyikan credential.

2. Manfaatkan System Prompts dan Message Truncation

Semakin panjang riwayat percakapan (messages), semakin besar context window yang dikirimkan dan semakin mahal biaya per-request. Untuk aplikasi produksi dengan percakapan panjang, potong array pesan di backend sebelum dikirim ke SDK:

typescript
// Ambil hanya 10 pesan terakhir untuk menghemat token cost
const recentMessages = messages.slice(-10);

3. Tangani Rate Limiting dengan Upstash Redis

Jika aplikasi kamu publik, pasang rate limiter berbasis IP address di Route Handler menggunakan @upstash/ratelimit dan @upstash/redis. Tanpa ini, bot atau scraper dapat menguras limit API kamu dalam hitungan detik.

4. Edge Runtime vs Serverless Runtime

  • Gunakan export const runtime = 'edge' jika target kamu adalah latency secepat mungkin dan respon pendek.
  • Gunakan Node.js Serverless runtime bawaan jika kamu butuh membaca file lokal, menggunakan SDK native Node.js yang kompleks, atau butuh timeout request yang lebih longgar (>30 detik).

Kesimpulan & Action Items

Vercel AI SDK 4.0 mendemokratisasi pembangunan fitur berbasis AI. Hal-hal kompleks seperti SSE protocol handling, React stream state management, dan multi-provider switching kini diselesaikan hanya dengan beberapa baris kode yang elegan.

Action Items untuk Kamu Sekarang:

  1. Clone atau jalankan create-next-app lalu install ai dan @ai-sdk/openai.
  2. Salin kode Route Handler dan Client Component dari artikel ini.
  3. Tambahkan OPENAI_API_KEY di .env.local dan jalankan npm run dev.
  4. Eksperimen dengan mengganti model ke anthropic('claude-3-5-sonnet-20240620') atau coba fitur generateObject dari AI SDK Core untuk menghasilkan keluaran berformat JSON terstruktur!

Butuh penyesuaian khusus untuk project Anda?

Jika situasi operasional atau arsitektur sistem bisnis Anda membutuhkan solusi kustom, diskusikan langsung bersama tim engineer kami. Anda juga bisa melihat rincian layanan vour.dev atau menghitung estimasi biaya project lebih dulu.

Mulai Project