Next.js Uygulamasına Yapay Zeka Entegrasyonu: Claude ve OpenAI API Kullanımı
Vercel AI SDK v6 ile Next.js 16 App Router'da Claude ve GPT-4o entegrasyonu. streamText, useChat hook, tool calling ve prompt caching konularında kapsamlı Türkçe rehber.

Önemli Not: Bu yazıdaki teknik bilgiler yazım tarihi itibarıyla geçerlidir. Kullanılan kütüphaneler, API'ler ve servisler zaman içinde değişebilir. Ücretlendirme, yasal düzenleme ve vergi konularında ilgili resmi kaynakları ve uzmanları referans alınız. Bu içerik bilgilendirme amaçlı olup herhangi bir finansal veya hukuki tavsiye niteliği taşımamaktadır.
2026'da bir web uygulamasına LLM (Large Language Model) entegrasyonu artık opsiyonel değil, rekabet gereği. Müşteri destek botu, içerik üreticisi, akıllı arama — tüm bu özellikleri Next.js App Router ile nasıl hayata geçirirsiniz?
Bu yazıda Vercel AI SDK v6 kullanarak Claude ve OpenAI API'lerini Next.js 16'ya entegre etmeyi adım adım ele alacağız. Hem streaming chat arayüzleri hem de server-side içerik üretimi için gerçek kod örnekleri bulacaksınız.
Neden Vercel AI SDK?
LLM API'lerini doğrudan çağırabilirsiniz — ancak bu pratikte külfetlidir:
- Her provider'ın farklı istek/yanıt formatı vardır
- Streaming yanıtları elle parse etmek karmaşıktır
- React state yönetimi tekrar tekrar yazılır
- Tool calling (function calling) implementasyonu süreci uzatır
Vercel AI SDK bu sorunları çözer: tek bir unified API ile Claude, GPT-4o, Gemini ve diğer modellere bağlanırsınız. Provider değiştirmek tek satır kod değişikliğidir.
SDK versiyonu hakkında kritik not: Bu yazı AI SDK v6 baz alınarak yazılmıştır. v5 ile gelen kırıcı değişiklikler (breaking changes) bu yazıda ele alınmaktadır — eğer eski bir projeyi güncelliyorsanız özellikle dikkat edin.
Türkiye'den API Erişimi
Başlamadan önce sık sorulan soruyu cevaplayalım: VPN gerekiyor mu?
- Anthropic (Claude): Türkiye resmi olarak desteklenen ülkeler listesindedir. API erişiminde VPN gerekmez. ✅
- OpenAI: OpenAI Türkiye'yi desteklemektedir. API, Türk IP adreslerinden erişilebilir. ✅
Ödeme için her iki provider da uluslararası kredi kartları kabul etmektedir. Türk Visa/Mastercard ile genellikle sorun yaşanmaz; yaşanması durumunda Wise sanal kart alternatif olarak kullanılabilir.
Güncel fiyatlar için: Anthropic API için anthropic.com/pricing, OpenAI için openai.com/pricing adresini inceleyin. Token fiyatları sık güncellendiğinden bu yazıda yer verilmemiştir.
Kurulum
bun add ai @ai-sdk/anthropic @ai-sdk/openai
Paket adları ve versiyonları (Mart 2026):
ai: v6.0.129 (core SDK)@ai-sdk/react: v3.0.131 (React hooks)@ai-sdk/anthropic: v3.0.46@ai-sdk/openai: v3.0.46
Ortam Değişkenleri
# .env.local
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
Zod ile type-safe env validasyonu (bu projede kullandığımız yöntem):
// lib/env.ts
import { z } from 'zod'
const envSchema = z.object({
ANTHROPIC_API_KEY: z.string().min(1).optional(),
OPENAI_API_KEY: z.string().min(1).optional(),
})
export const env = envSchema.parse(process.env)
generateText vs streamText: Ne Zaman Hangisi?
AI SDK'nın iki temel fonksiyonu vardır ve doğru seçim kullanım senaryonuza bağlıdır.
generateText — Tek Seferlik İstek
import { generateText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
const { text } = await generateText({
model: anthropic('claude-sonnet-4-6'),
prompt: 'Next.js 16 nedir? 2 cümleyle açıkla.',
})
console.log(text)
Ne zaman kullanılır:
- Sunucu tarafında içerik üretimi (ürün açıklamaları, meta description)
- Bir kez çalışan batch işlemleri
- Yapılandırılmış veri çıkarma
- Kullanıcının beklemediği arka plan görevleri
streamText — Gerçek Zamanlı Streaming
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
const result = await streamText({
model: anthropic('claude-sonnet-4-6'),
prompt: 'Türkiye'nin başkenti hakkında kısa bir paragraf yaz.',
})
// ⚠️ StreamingTextResponse artık YOK (v5+ kırıcı değişiklik)
return result.toUIMessageStreamResponse()
Ne zaman kullanılır:
- Chat arayüzleri
- Uzun metin üretimi (kullanıcı anlık geri bildirim bekler)
- İnteraktif asistanlar
v4'ten Geçiş Yapıyorsanız:
StreamingTextResponsev5'ten itibaren kaldırıldı. Yerineresult.toUIMessageStreamResponse()kullanın. Bu yaygın bir migration hatasıdır.
Chat API Route Handler Kurulumu
Next.js App Router'da chat endpoint'i oluşturmak:
// app/api/chat/route.ts
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
export async function POST(request: Request) {
const { messages } = await request.json()
const result = await streamText({
model: anthropic('claude-sonnet-4-6'),
system: `Sen yardımcı bir asistansın.
Türkçe yanıt ver.
Kısa ve öz ol.`,
messages,
})
return result.toUIMessageStreamResponse()
}
OpenAI kullanmak için tek satır değiştirmeniz yeterli:
import { openai } from '@ai-sdk/openai'
// anthropic('claude-sonnet-4-6') yerine:
model: openai('gpt-4o')
useChat Hook: Chat Arayüzü
v5+ Kırıcı Değişiklik:
useChatartık'ai/react'yerine'@ai-sdk/react'paketinden import edilir.
// app/components/chat.tsx
'use client'
import { useChat } from '@ai-sdk/react' // ← Yeni import path
export function Chat() {
const { messages, input, handleInputChange, handleSubmit, status } = useChat({
api: '/api/chat',
})
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<div className="flex-1 overflow-y-auto space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={
message.role === 'user'
? 'text-right'
: 'text-left'
}
>
<span
className={
message.role === 'user'
? 'bg-blue-500 text-white px-3 py-2 rounded-lg inline-block'
: 'bg-gray-100 px-3 py-2 rounded-lg inline-block'
}
>
{typeof message.content === 'string'
? message.content
: null}
</span>
</div>
))}
{status === 'streaming' && (
<div className="text-gray-400 text-sm">Yazıyor...</div>
)}
</div>
<form onSubmit={handleSubmit} className="flex gap-2 mt-4">
<input
value={input}
onChange={handleInputChange}
placeholder="Mesajınızı yazın..."
className="flex-1 border rounded-lg px-3 py-2"
disabled={status === 'streaming'}
/>
<button
type="submit"
disabled={status === 'streaming'}
className="bg-blue-500 text-white px-4 py-2 rounded-lg disabled:opacity-50"
>
Gönder
</button>
</form>
</div>
)
}
useChat Hook'un Yeni API'si (v5+)
v5 ile birlikte useChat hook'una yeni alanlar eklendi:
const {
messages,
input,
handleInputChange,
handleSubmit,
status, // 'idle' | 'streaming' | 'submitted' | 'ready'
stop, // Streaming'i durdur
reload, // Son mesajı yeniden üret (eski: regenerate)
sendMessage, // Programatik mesaj gönderme
error, // Hata nesnesi
} = useChat({ api: '/api/chat' })
Model Seçimi: Claude vs GPT-4o
Claude Model Ailesinde Seçim
// Hız + maliyet optimizasyonu
model: anthropic('claude-haiku-4-5')
// Denge (çoğu senaryo için önerilir)
model: anthropic('claude-sonnet-4-6')
// Maksimum kalite
model: anthropic('claude-opus-4-6')
Kullanım rehberi:
- Haiku 4.5: Basit sınıflandırma, kısa yanıtlar, yüksek hacimli görevler
- Sonnet 4.6: Chat uygulamaları, içerik üretimi, kod yardımı (bu projenin tercihi)
- Opus 4.6: Karmaşık analiz, uzun belgeler, zincir düşünme gerektiren görevler
GPT-4o ile Çalışmak
import { openai } from '@ai-sdk/openai'
// Güncel modeller
model: openai('gpt-4o') // En kapsamlı
model: openai('gpt-4o-mini') // Hız + maliyet
model: openai('o3-mini') // Akıl yürütme görevleri
Güncel model listesi ve fiyatlar için her zaman resmi dokümantasyonu kontrol edin.
Tool Calling: LLM'i Dış Dünyaya Bağlama
Tool calling, LLM'in sizin tanımladığınız fonksiyonları çağırmasına olanak tanır. Bunu şöyle düşünün: modele araçlar veriyorsunuz, o da doğru araçları doğru zamanda kullanıyor.
Basit Bir Örnek: Ürün Arama
// app/api/chat/route.ts
import { streamText, tool, stepCountIs } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
import { z } from 'zod'
export async function POST(request: Request) {
const { messages } = await request.json()
const result = await streamText({
model: anthropic('claude-sonnet-4-6'),
system: 'Sen bir e-ticaret asistanısın. Ürün aramak için araçlarını kullan.',
messages,
tools: {
searchProducts: tool({
description: 'Ürün kataloğunda arama yapar',
inputSchema: z.object({
query: z.string().describe('Arama terimi'),
maxPrice: z.number().optional().describe('Maksimum fiyat (TL)'),
category: z.string().optional().describe('Ürün kategorisi'),
}),
execute: async ({ query, maxPrice, category }) => {
// Gerçek uygulamada veritabanı sorgusu
const products = await db.query.products.findMany({
where: and(
like(products.name, `%${query}%`),
maxPrice ? lte(products.price, maxPrice) : undefined,
category ? eq(products.category, category) : undefined,
),
limit: 5,
})
return { products, total: products.length }
},
}),
getProductDetails: tool({
description: 'Bir ürünün detaylarını getirir',
inputSchema: z.object({
productId: z.string(),
}),
execute: async ({ productId }) => {
return await db.query.products.findFirst({
where: eq(products.id, productId),
})
},
}),
},
stopWhen: stepCountIs(5), // Maksimum 5 tool çağrısı
})
return result.toUIMessageStreamResponse()
}
Neden Tool Calling Kullanılır?
Tool calling olmadan LLM yalnızca eğitim verilerine dayanır — güncel veri yoktur. Tool calling ile:
- Veritabanı sorguları: Anlık ürün/sipariş bilgisi
- Harici API: Hava durumu, döviz kuru, kargo takibi
- Hesaplamalar: Kesin matematik hesapları
- Eylemler: E-posta gönderme, form doldurma, rezervasyon
Prompt Caching: Maliyet Optimizasyonu
Bir chat uygulamasında sistem promptu her istekte tekrar gönderilir. Uzun bir sistem promptu varsa bu maliyeti önemli ölçüde artırır. Claude'un prompt caching özelliği bunu çözer.
Nasıl Çalışır?
Cachelemek istediğiniz içeriğe cacheControl anotasyonu eklersiniz. İlk istekte Claude bu içeriği cache'e yazar; sonraki isteklerde çok daha düşük maliyetle okunur.
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
// Uzun sistem promptu veya döküman
const LONG_SYSTEM_CONTEXT = `
Sen ilkkod.com'un müşteri hizmetleri asistanısın.
[Burada yüzlerce satır şirket bilgisi, ürün detayları,
SSS yanıtları, politikalar yer alabilir...]
`
export async function POST(request: Request) {
const { messages } = await request.json()
const result = await streamText({
model: anthropic('claude-sonnet-4-6'),
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: LONG_SYSTEM_CONTEXT,
providerOptions: {
anthropic: {
cacheControl: { type: 'ephemeral' }, // Cache işareti
},
},
},
{
type: 'text',
text: messages[messages.length - 1].content as string,
},
],
},
],
})
return result.toUIMessageStreamResponse()
}
Cache TTL ve Tasarruf
| Cache TTL | Write Maliyeti | Read Maliyeti |
|---|---|---|
| 5 dakika (varsayılan) | 1.25x temel | 0.1x temel (%90 tasarruf) |
| 1 saat | 2x temel | 0.1x temel (%90 tasarruf) |
Caching için minimum token eşikleri (bu eşiğin altındaki içerikler cache'lenmez):
- Claude Sonnet 4.6: 2.048 token
- Claude Opus 4.6/4.5, Haiku 4.5: 4.096 token
Pratik Senaryo 1: Müşteri Destek Botu
Türkçe konuşan bir müşteri destek botu oluşturalım:
// app/api/destek/route.ts
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
const SYSTEM_PROMPT = `Sen [Şirket Adı]'nın müşteri hizmetleri asistanısın.
Görevin:
- Müşterilere sipariş, iade ve ürün konularında yardımcı olmak
- Yanıtlarını kısa ve net tutmak
- Bilmediğin konularda müşteriyi ilgili departmana yönlendirmek
- Her zaman Türkçe yanıt vermek
- Nazik ve profesyonel olmak
Bilmediğin bir şeyi asla uydurmayacaksın. "Bu konuda sizi müşteri
hizmetlerimizle (0850 XXX XX XX) görüştüreyim" de.`
export async function POST(request: Request) {
const { messages } = await request.json()
// Basit rate limiting (production'da Redis ile yapın)
const userIp = request.headers.get('x-forwarded-for') ?? 'unknown'
const isAllowed = await checkRateLimit(userIp)
if (!isAllowed) {
return new Response('Çok fazla istek', { status: 429 })
}
const result = await streamText({
model: anthropic('claude-haiku-4-5'), // Destek botu için Haiku yeterli
system: SYSTEM_PROMPT,
messages,
maxTokens: 500, // Kısa yanıtlar için sınır
})
return result.toUIMessageStreamResponse()
}
async function checkRateLimit(ip: string): Promise<boolean> {
// Örnek implementasyon — production'da Upstash Redis kullanın
return true
}
Pratik Senaryo 2: Ürün Açıklaması Üreticisi
E-ticaret için Server Action ile içerik üretimi:
// app/actions/generate-description.ts
'use server'
import { generateText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
interface ProductInput {
name: string
features: string[]
targetAudience: string
tone: 'profesyonel' | 'samimi' | 'eğlenceli'
}
export async function generateProductDescription(
product: ProductInput
): Promise<{ description: string; seoTitle: string }> {
const prompt = `
Aşağıdaki ürün için Türkçe açıklama yaz:
Ürün: ${product.name}
Özellikler: ${product.features.join(', ')}
Hedef kitle: ${product.targetAudience}
Ton: ${product.tone}
Şu formatta JSON döndür:
{
"description": "150-200 kelimelik ürün açıklaması",
"seoTitle": "SEO uyumlu ürün başlığı (max 60 karakter)"
}
`
const { text } = await generateText({
model: anthropic('claude-sonnet-4-6'),
prompt,
})
// JSON parse — production'da daha güvenli parse kullanın
return JSON.parse(text)
}
Server Component'tan kullanım:
// app/admin/products/new/page.tsx
import { generateProductDescription } from '@/app/actions/generate-description'
export default function NewProductPage() {
async function handleGenerate(formData: FormData) {
'use server'
const description = await generateProductDescription({
name: formData.get('name') as string,
features: (formData.get('features') as string).split(','),
targetAudience: formData.get('audience') as string,
tone: 'profesyonel',
})
// Veritabanına kaydet...
}
return (
<form action={handleGenerate}>
{/* Form alanları */}
</form>
)
}
Güvenlik: API Key Yönetimi
LLM entegrasyonunda en kritik konu API key güvenliğidir.
Yapılması Gerekenler
1. API key'i sadece server tarafında kullanın:
// ✅ Doğru — Route Handler veya Server Action
export async function POST(request: Request) {
// API key sunucuda, istemciye hiç gönderilmiyor
const result = await generateText({
model: anthropic('claude-sonnet-4-6'),
// ANTHROPIC_API_KEY env var otomatik okunur
})
}
// ❌ Yanlış — Client Component'ta API key kullanmayın
'use client'
const response = await fetch('https://api.anthropic.com/...', {
headers: { 'x-api-key': process.env.ANTHROPIC_API_KEY } // Client bundle'a sızar!
})
2. Kullanıcı başına kota uygulayın:
// app/api/chat/route.ts
import { auth } from '@/lib/auth'
import { headers } from 'next/headers'
export async function POST(request: Request) {
// Oturum kontrolü
const session = await auth.api.getSession({
headers: await headers(),
})
if (!session) {
return new Response('Yetkisiz', { status: 401 })
}
// Kullanıcı kotası kontrolü (örnek — Redis ile implemente edin)
const dailyUsage = await getUserDailyUsage(session.user.id)
if (dailyUsage >= 50) {
return new Response('Günlük limit aşıldı', { status: 429 })
}
const { messages } = await request.json()
// Maksimum mesaj sayısı sınırı
const recentMessages = messages.slice(-10) // Son 10 mesaj
const result = await streamText({
model: anthropic('claude-haiku-4-5'),
messages: recentMessages,
})
await incrementUserUsage(session.user.id)
return result.toUIMessageStreamResponse()
}
3. Input validasyonu:
import { z } from 'zod'
const chatSchema = z.object({
messages: z.array(
z.object({
role: z.enum(['user', 'assistant']),
content: z.string().max(4000), // Maksimum mesaj uzunluğu
})
).max(20), // Maksimum geçmiş derinliği
})
export async function POST(request: Request) {
const body = await request.json()
const { messages } = chatSchema.parse(body) // Hatalı input hata fırlatır
// ...
}
Türkçe Dil Performansı
Hangi model Türkçe'de daha iyi? Resmi benchmark olmamakla birlikte pratik deneyimler:
Claude (Anthropic):
- Türkçe dilbilgisi ve doğallık açısından güçlü
- Uzun Türkçe belgeler üretmede başarılı
- Türkçe karakter sorunları (ı, ş, ç, ğ, ö, ü) yok
GPT-4o (OpenAI):
- Türkçe içerik üretiminde güvenilir
- Özellikle kısa yanıtlarda hızlı
Pratik öneri: Sistem promptunu İngilizce, kullanıcı yanıtlarını Türkçe bekleyecek şekilde yazın:
system: `You are a helpful customer support assistant.
Always respond in Turkish (Turkish language).
Use formal Turkish (siz, sizin, size) unless the user writes informally.
Never switch to English even if the user writes in English.`
İzleme ve Observability
Production'da API kullanımını izlemek önemlidir. LangFuse Türkiye'den erişilebilen açık kaynaklı bir seçenektir:
bun add langfuse
import { Langfuse } from 'langfuse'
const langfuse = new Langfuse({
publicKey: env.LANGFUSE_PUBLIC_KEY,
secretKey: env.LANGFUSE_SECRET_KEY,
})
// Her LLM çağrısını trace etmek için:
const trace = langfuse.trace({ name: 'customer-support-chat' })
const generation = trace.generation({
name: 'response',
model: 'claude-sonnet-4-6',
input: messages,
})
// Yanıt geldikten sonra:
generation.end({ output: responseText })
LangFuse şunları sağlar:
- Token kullanımı ve maliyet takibi
- Latency metrikleri
- Başarısız istek logları
- Prompt versiyonlama
Sıkça Sorulan Sorular
Vercel AI SDK v4 projesini v6'ya nasıl migrate ederim?
İki kritik değişiklik var:
- useChat import:
'ai/react'→'@ai-sdk/react' - StreamingTextResponse:
return new StreamingTextResponse(result.textStream)→return result.toUIMessageStreamResponse()
Bunların dışında generateText, streamText ve provider import'ları aynı kaldı.
Claude API ve OpenAI API'yi aynı projede kullanabilir miyim?
Evet, sorunsuz çalışır. Her Route Handler farklı bir provider kullanabilir:
// Hızlı yanıtlar için OpenAI
model: openai('gpt-4o-mini')
// Kaliteli içerik için Claude
model: anthropic('claude-sonnet-4-6')
Chat geçmişini veritabanına nasıl kaydederim?
useChat hook'un onFinish callback'ini kullanın:
const { messages } = useChat({
api: '/api/chat',
onFinish: async (message) => {
await fetch('/api/chat/save', {
method: 'POST',
body: JSON.stringify({ message }),
})
},
})
Streaming olmadan kullanabilir miyim?
Evet, generateText ile:
export async function POST(request: Request) {
const { prompt } = await request.json()
const { text } = await generateText({
model: anthropic('claude-haiku-4-5'),
prompt,
})
return Response.json({ text })
}
Tool calling ile kaç araç tanımlayabilirim?
Teknik limit yoktur ancak çok fazla araç modelin kararsız kalmasına neden olabilir. Pratik olarak 5-10 araç iyi çalışır. Her araç için açıklayıcı description yazın — model hangi aracı ne zaman kullanacağına buna bakarak karar verir.
API maliyetlerini nasıl kontrol ederim?
- maxTokens parametresiyle maksimum yanıt uzunluğunu sınırlayın
- Basit görevler için Haiku/GPT-4o-mini kullanın
- Prompt caching'i etkinleştirin (uzun sistem promptları için %90 tasarruf)
- Kullanıcı başına günlük kota uygulayın
- Güncel fiyatlar ve optimize etme önerileri için: anthropic.com/pricing ve openai.com/pricing
Sonraki Adımlar
Bu yazıda Vercel AI SDK v6 ile temel entegrasyonu ele aldık. İleri konular için:
- Structured Output: JSON schema ile tip güvenli LLM çıktısı
- RAG (Retrieval Augmented Generation): Kendi dokümanlarınızı vektör veritabanında indexleyip LLM'e bağlama
- Agentic Workflows: Birden fazla modelin koordineli çalıştığı otomatik görev sistemleri
- Image Input: Görsel analizi için Claude ve GPT-4o Vision
Yapay zeka entegrasyonu olan bir Next.js projesi için yardım almak ister misiniz? İletişim sayfamızdan bize ulaşın veya proje teklif formunu doldurun.


