📒 Ridwan Aji Bari - Blog

Memahami useChat di Vercel AI SDK v6: Dari Konsep Dasar hingga Menengah

1 Juli 2026•7 menit baca

Membangun aplikasi berbasis kecerdasan buatan (AI) seperti chatbot interaktif kini menjadi kebutuhan standar di berbagai platform web. Namun, jika Anda mencoba membuat fitur chat dari nol, Anda akan menyadari bahwa mengelola state percakapan, menangani streaming text secara real-time, dan menjaga antarmuka tetap sinkron dengan server adalah pekerjaan yang sangat rumit.

Untuk menyederhanakan proses ini, Vercel AI SDK hadir sebagai pustaka (library) standar industri. Pada versi terbaru, Vercel AI SDK v6, terjadi perombakan arsitektur besar-besaran pada hook useChat. Di artikel ini, kita akan mempelajari arsitektur baru tersebut, mulai dari pemisahan transport, siklus hidup (lifecycle), hingga implementasi kode dasar dan tingkat menengah yang sesuai dengan standar dokumentasi terbaru.


1. Arsitektur Vercel AI SDK v6: Core vs UI

Sebelum menulis kode, penting untuk memahami bagaimana Vercel AI SDK membagi tugasnya:

  1. AI SDK Core (ai): Berjalan di sisi server (Node.js/Edge Runtime). Menyediakan fungsi seperti streamText dan toUIMessageStream untuk berinteraksi langsung dengan LLM Provider.
  2. AI SDK UI (@ai-sdk/react): Berjalan di sisi klien (browser). Menyediakan hook UI tingkat tinggi (high-level UI hooks) seperti useChat untuk menjembatani stream data dari server ke antarmuka React secara instan.

Di versi 6, koneksi antara sisi klien dan server diabstraksikan melalui Chat Transport. Defaultnya menggunakan DefaultChatTransport, yang bertugas mengatur permintaan HTTP POST ke endpoint backend kita.


2. Mengapa Menggunakan useChat Terbaru?

Berbeda dengan versi lama yang memaksakan state input dikelola secara internal oleh hook, versi terbaru memberikan kebebasan penuh bagi pengembang untuk mengelola state input secara mandiri menggunakan React useState biasa.

Selain itu, pesan dalam useChat versi terbaru kini disimpan dalam bentuk parts (bukan hanya string datar content). Struktur ini mendukung jenis konten yang kaya, seperti:

  • text: Teks jawaban utama.
  • reasoning: Proses berpikir model (seperti pada DeepSeek R1).
  • tool-call & tool-result: Panggilan fungsi/alat eksternal.

3. Implementasi Sisi Backend (api/chat/route.ts)

Sebelum menulis kode React di sisi klien, kita perlu menyiapkan endpoint API di sisi server untuk memproses pesan dan mengirimkan aliran data (stream) yang dapat dibaca oleh transport klien. Berikut adalah implementasi backend standar menggunakan Vercel AI SDK v6:

import { streamText, createUIMessageStreamResponse, toUIMessageStream, convertToModelMessages } from "ai";
import { openai } from "@ai-sdk/openai";

export const maxDuration = 30;

export async function POST(req: Request) {
  // 1. Ambil riwayat percakapan yang dikirim oleh DefaultChatTransport klien
  const { messages } = await req.json();

  // 2. Konversi UIMessage[] dari klien ke CoreMessage[] yang dimengerti oleh LLM model
  const result = streamText({
    model: openai("gpt-4o"),
    system: "Kamu adalah asisten AI yang cerdas dan suportif.",
    messages: await convertToModelMessages(messages),
  });

  // 3. Gunakan helper untuk mengemas stream ke format yang dikenali oleh UI hook
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}

Penempatan dan Penamaan Kunci API (API Key)

Untuk menjalankan model AI di atas, Anda perlu mengonfigurasi kunci API. Vercel AI SDK v6 mendukung dua opsi penempatan kunci:

Opsi A: Sisi Server (Menggunakan Environment Variables - Direkomendasikan)

Cara termudah adalah menaruh kunci API di sisi backend server menggunakan file .env atau .env.local pada folder root proyek Anda. SDK akan membacanya secara otomatis selama variabel lingkungan memiliki nama standar berikut:

  • OpenAI: OPENAI_API_KEY=sk-proj-...
  • Google Gemini: GOOGLE_GENERATIVE_AI_API_KEY=AIzaSy...
  • Anthropic Claude: ANTHROPIC_API_KEY=sk-ant-...
  • DeepSeek: DEEPSEEK_API_KEY=sk-...
  • xAI Grok: XAI_API_KEY=xai-...

Opsi B: Dinamis dari Klien (Dynamic User Keys)

Jika Anda mengizinkan pengguna akhir memasukkan kunci API mereka sendiri (misalnya disimpan di localStorage klien), masukkan kunci tersebut ke dalam parameter body dari DefaultChatTransport klien. Di sisi server, inisialisasi provider menggunakan fungsi pembuat dinamis seperti createOpenAI({ apiKey }) atau createDeepSeek({ apiKey }).


4. Implementasi Sisi Frontend (React UI)

Setelah backend siap, kita bisa menghubungkan antarmuka React menggunakan useChat dan DefaultChatTransport untuk memicu stream tersebut:

'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';

export default function ChatComponent() {
  // 1. Inisialisasi useChat dengan DefaultChatTransport
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat', // Endpoint server Anda
    }),
  });

  // 2. State input dikelola mandiri menggunakan React useState
  const [input, setInput] = useState('');

  return (
    <div className="chat-container">
      <div className="messages-list">
        {messages.map((message) => (
          <div key={message.id} className={`message ${message.role}`}>
            <strong>{message.role === 'user' ? 'User: ' : 'AI: '}</strong>
            
            {/* 3. Render isi pesan wajib melalui pemetaan parts */}
            {message.parts.map((part, index) => {
              if (part.type === 'text') {
                return <span key={index}>{part.text}</span>;
              }
              // Dukungan opsional untuk menampilkan penalaran / reasoning tokens
              if (part.type === 'reasoning') {
                return (
                  <span key={index} className="text-zinc-500 italic block my-1">
                    Thinking: {part.text}
                  </span>
                );
              }
              return null;
            })}
          </div>
        ))}
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          if (input.trim()) {
            // 4. Kirim pesan menggunakan fungsi sendMessage
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Tulis pesan..."
          disabled={status !== 'ready'} // 5. Cek status pemrosesan
        />
        <button type="submit" disabled={status !== 'ready' || !input.trim()}>
          Kirim
        </button>
      </form>
    </div>
  );
}

Properti Utama yang Dihasilkan:

  • messages: Array berisi pesan bertipe UIMessage. Setiap pesan memiliki properti parts yang berisi pecahan konten.
  • sendMessage: Fungsi untuk mengirimkan pesan baru ke transport. Mengambil objek { text: string }.
  • status: Menandakan status pemrosesan asisten. Nilainya dapat berupa 'ready' | 'loading' | 'streaming' | 'submitted' | 'error'.

5. Siklus Hidup (Lifecycle) Request useChat

Memahami lifecycle di balik layar useChat sangat penting untuk menangani pembatalan stream atau penanganan error.

Berikut adalah diagram alur jalannya pesan:

sequenceDiagram
    participant UI as Browser (useChat)
    participant Server as Backend Route (api/chat)
    
    UI->>UI: 1. Form Submit memanggil sendMessage({ text })
    UI->>UI: 2. Optimistic Update (Pesan User ditambahkan ke state parts)
    UI->>Server: 3. POST Request dikirim via DefaultChatTransport (Daftar pesan terlampir)
    Server->>Server: 4. Server memproses stream via streamText()
    Server->>UI: 5. Connection Established (Stream Protocol dimulai)
    loop Selama Stream Berjalan
        Server->>UI: 6. Teks Parsial (Chunks) Terkirim
        UI->>UI: UI Memperbarui status 'streaming' & menggabungkan parts text
    end
    Note over UI,Server: 7. Stream Selesai (status kembali ke 'ready') atau Terjadi Error (status 'error')

Penjelasan Detail Tiap Fase:

Fase 1: Pemicu (Trigger)

Ketika pengguna menyerahkan input dan memicu sendMessage({ text: input }), hook memvalidasi data dan mengubah status dari 'ready' menjadi 'submitted'.

Fase 2: Pembaruan Optimistik (Optimistic Update)

useChat langsung menyuntikkan objek pesan user baru ke dalam array messages dengan tipe part 'text'. Ini membuat UI langsung terisi tanpa menunggu respons server.

Fase 3: Pengiriman Permintaan (Dispatch Request)

DefaultChatTransport melakukan pemanggilan HTTP POST ke endpoint backend, melampirkan seluruh riwayat percakapan. Pada tahap ini, status berubah menjadi 'loading'.

Fase 4: Koneksi & Streaming (Connection & Streaming)

Koneksi server terjalin dan data mengalir kembali ke browser. status berubah menjadi 'streaming'. Setiap fragmen data yang diterima diakumulasikan ke dalam part bertipe 'text' milik pesan asisten.

Fase 5: Penyelesaian (Completion)

Ketika server selesai mengirim seluruh data, koneksi ditutup. status kembali menjadi 'ready', dan callback onFinish (jika didefinisikan) akan dipicu.


6. Implementasi Fitur Tingkat Menengah

Berikut beberapa pola lanjutan untuk menyesuaikan transport dan payload data:

A. Mengirim Custom Parameter/Payload

Untuk mengirim parameter tambahan (seperti nama model atau kunci API dinamis dari client), definisikan parameter tersebut di dalam opsi body pada DefaultChatTransport:

const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    body: {
      provider: 'openai',
      model: 'gpt-4o',
      apiKey: userApiKey, // Dikirim ke server di setiap request
    },
  }),
});

B. Menghentikan Streaming Secara Paksa (stop)

Anda bisa membatalkan streaming yang sedang berjalan menggunakan properti stop:

const { status, stop } = useChat({ ... });

return (
  <div>
    {status === 'streaming' && (
      <button onClick={() => stop()}>Hentikan Generasi</button>
    )}
  </div>
);

C. Menampilkan Indikator Loading & Error

Dengan state status, penanganan visual menjadi sangat presisi:

const { status, error } = useChat({ ... });

return (
  <div>
    {status === 'loading' && <p>Menghubungkan ke asisten...</p>}
    {status === 'error' && <p className="text-red-500">Error: {error?.message}</p>}
  </div>
);

7. Ringkasan

Vercel AI SDK v6 mendesain ulang antarmuka useChat untuk memberikan kontrol yang lebih bersih kepada pengembang frontend. Dengan pemisahan Chat Transport, pengelolaan state input mandiri, dan visualisasi pesan berbasis parts, aplikasi chat Anda kini jauh lebih fleksibel dan siap untuk mendukung model-model penalaran (reasoning) modern secara optimal.

Selamat menulis kode AI!