Next.js Server Actions, bir formu işlemek için ayrı bir API route'u yazma zorunluluğunu ortadan kaldırır: mutasyon mantığını doğrudan sunucuda çalışan bir fonksiyona koyar ve formu o fonksiyona bağlarsın. Sonuç olarak fetch çağrısı, manuel JSON serileştirme ve ayrı bir app/api/... dosyası yazmadan veri oluşturup güncelleyebilirsin. Bu yazıda App Router üzerinde Server Actions'ı sıfırdan kuruyor, doğrulama ve hata yönetimini ekliyor ve üretimde dikkat etmen gereken noktaları anlatıyorum.
Server Action nedir, nasıl çalışır?
Server Action, gövdesinin başında 'use server' direktifi bulunan asenkron bir fonksiyondur. Bu fonksiyon yalnızca sunucuda çalışır; kodu istemci paketine (bundle) dahil edilmez. Next.js, formu gönderdiğinde tarayıcıyla sunucu arasındaki ağ çağrısını senin yerine kurar. Sen sadece fonksiyonu yazarsın, Next.js onu çağrılabilir bir uç noktaya dönüştürür.
İki temel yerleştirme biçimi vardır: ayrı bir dosyada modül düzeyinde 'use server' ile, ya da bir Server Component içinde satır içi tanımlanmış olarak. Tekrar kullanılabilir, test edilebilir kod için ayrı dosya yaklaşımını tercih ederim.
İlk Server Action'ını yaz
Önce aksiyonu kendi dosyasına koyalım. Dosyanın en üstündeki 'use server', içindeki tüm dışa aktarılan fonksiyonları Server Action yapar:
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const body = formData.get('body')
// burada veritabanına yazarsın (Prisma, Drizzle, vb.)
await db.post.create({ data: { title, body } })
revalidatePath('/posts')
redirect('/posts')
}
Form tarafı ise şaşırtıcı derecede sade. action niteliğine bir URL değil, doğrudan fonksiyonu veriyorsun:
// app/posts/new/page.tsx
import { createPost } from '@/app/actions'
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<textarea name="body" />
<button type="submit">Kaydet</button>
</form>
)
}
Dikkat et: form bir Server Component içinde yaşayabilir ve hiç JavaScript indirilmeden bile çalışır. Bu, ilerici geliştirmenin (progressive enhancement) doğal bir sonucudur.
Girişi doğrula — asla ham veriye güvenme
Server Action, internete açık bir uç noktadır; istemcide ne gösterdiğinden bağımsız olarak doğrudan çağrılabilir. Bu yüzden gelen FormData'yı her zaman sunucuda doğrula. Zod ile şema tabanlı doğrulama temiz bir yöntem:
'use server'
import { z } from 'zod'
const PostSchema = z.object({
title: z.string().min(3, 'Başlık en az 3 karakter olmalı'),
body: z.string().min(10),
})
export async function createPost(prevState, formData: FormData) {
const parsed = PostSchema.safeParse({
title: formData.get('title'),
body: formData.get('body'),
})
if (!parsed.success) {
return { errors: parsed.error.flatten().fieldErrors }
}
await db.post.create({ data: parsed.data })
revalidatePath('/posts')
return { success: true }
}
safeParse hata fırlatmak yerine bir sonuç nesnesi döndürür; böylece doğrulama hatalarını kullanıcıya geri gönderebilirsin.
Hata ve durum yönetimi: useActionState
Yukarıdaki aksiyonun döndürdüğü hataları forma bağlamak için React'in useActionState hook'unu kullanırız (React 19 / Next.js 15 ile bu addadır; daha eski sürümlerde useFormState idi). Hook bir istemci bileşeni gerektirir:
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
export function PostForm() {
const [state, formAction, pending] = useActionState(createPost, {})
return (
<form action={formAction}>
<input name="title" />
{state?.errors?.title && <p>{state.errors.title}</p>}
<button disabled={pending}>
{pending ? 'Kaydediliyor...' : 'Kaydet'}
</button>
</form>
)
}
Üçüncü dönüş değeri olan pending, aksiyon çalışırken true olur ve butonu devre dışı bırakmak için birebir uygundur. Daha granüler yükleme göstergeleri için useFormStatus hook'unu ayrı bir buton bileşeninde kullanabilirsin.
Önbellek tazeleme: revalidatePath ve revalidateTag
Bir mutasyondan sonra ekranın güncel veriyi göstermesi gerekir. Next.js veriyi agresif şekilde önbelleğe aldığı için, değişen yolu açıkça geçersiz kılarsın:
revalidatePath('/posts')— belirli bir rotanın önbelleğini temizler.revalidateTag('posts')—fetchçağrılarını etiketlediysen, o etikete bağlı tüm verileri tazeler.redirect('/posts')— mutasyon sonrası kullanıcıyı başka sayfaya yönlendirir; bunutry/catchbloğunun dışında çağır, çünkü içeride özel bir hata fırlatır.
Üretimde dikkat edilmesi gerekenler
- Yetkilendirme zorunlu: Aksiyon bir uç noktadır. Her aksiyonun başında oturumu/yetkiyi kontrol et; UI'da butonu gizlemek güvenlik değildir.
- Yan etkileri ayır: Veritabanı, e-posta, ödeme gibi işleri aksiyonun çağırdığı saf fonksiyonlara taşı; aksiyon ince bir katman kalsın.
- Dönüş değerleri serileştirilebilir olmalı: İstemciye dönen state düz nesne/dizi olmalı; sınıf örnekleri veya fonksiyon döndürme.
- Yine de API gerekebilir: Mobil uygulama veya üçüncü taraf entegrasyonu varsa, Server Action onların yerini tutmaz; gerçek bir API'ye ihtiyacın olur.
Sık Sorulan Sorular
Server Actions yalnızca formlarla mı kullanılır?
Hayır. action niteliğine bağlamak en yaygın yol olsa da, bir Server Action'ı bir buton onClick işleyicisinden veya startTransition içinden de çağırabilirsin. Form bağlama, JavaScript devre dışıyken bile çalıştığı için tercih edilir.
API route'larını tamamen bırakmalı mıyım?
Hayır. Server Actions, kendi UI'ndan tetiklenen mutasyonlar için idealdir. Webhook'lar, harici istemciler veya genel bir REST/JSON arabirimi gerektiğinde klasik route handler'lar (app/api/...) hâlâ doğru araçtır.
Server Actions güvenli mi?
Altyapı güvenlidir — Next.js, eylem kimliklerini korur ve gönderimleri şifreli referanslarla eşler. Ancak iş mantığının güvenliği sana aittir: girişi doğrula ve yetki kontrolü yap. Bunlar olmadan açık bir uç nokta bırakmış olursun.
Projende formlar ağ kodu yüzünden mi karmaşıklaşıyor? Next.js Server Actions ile temiz, doğrulanmış ve güvenli bir veri katmanı kurmana yardımcı olabilirim — benimle iletişime geç.