Next.js 14 ile birlikte gelen Server Actions, form gönderimlerini sunucu tarafında güvenle işlemek ve istemci kodunu sadeleştirmek için güçlü bir seçenek sunar. Bu rehberde, e-posta alanı içeren basit bir formu ve güvenli dosya yükleme akışını, Zod ile doğrulama yaparak adım adım kuracağız. Hedefimiz, gereksiz API katmanları oluşturmadan, tek bir action ile temiz ve ölçeklenebilir bir çözüm elde etmek.
Server Actions yaklaşımı; XHR/fetch kodlarını azaltır, React Server Components ile uyumlu çalışır ve SSR/ISR stratejilerine zarar vermeden form iş akışlarını basitleştirir. Dosya işlemlerinde ise tür ve boyut kontrolüyle güvenlik yüzeyini daraltır. Vercel gibi platformlarda çalışırken /tmp gibi geçici dizinlerin kullanımı mümkündür; kalıcı depolama için S3 benzeri servisler önerilir.
Gereksinimler
- Node.js 18+ ve Next.js 14+
- Paket yöneticisi (pnpm, npm veya yarn)
- (İsteğe bağlı) Zod ile şema tabanlı doğrulama
Kurulum ve Hazırlık
Yeni bir proje başlatalım:
npx create-next-app@latest next14-server-actions
cd next14-server-actions
pnpm add zod
Server Actions Next.js 14’te varsayılan olarak desteklenir. Projenizde gerekirse aşağıdaki gibi bir yapılandırma ile istek boyutunu kontrol edebilirsiniz (opsiyonel):
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: {
bodySizeLimit: '6mb' // FormData sınırı (opsiyonel)
}
}
};
export default nextConfig;
Server Action: Doğrulama ve Yükleme
Aşağıdaki action, e-postayı Zod ile doğrular, dosya türünü ve boyutunu kontrol eder, ardından dosyayı bir hedef dizine yazar. Geliştirme ve Vercel üzerinde test için varsayılan olarak /tmp kullanılabilir.
// app/actions.ts
'use server';
import { z } from 'zod';
import { writeFile } from 'fs/promises';
import path from 'path';
import crypto from 'crypto';
const schema = z.object({
email: z.string().email()
});
// Node.js API'larına ihtiyacımız olduğundan runtime'ı Node olarak zorlayalım (opsiyonel).
export const runtime = 'nodejs';
export async function uploadFile(formData: FormData) {
const email = String(formData.get('email') || '');
const file = formData.get('file');
const parsed = schema.safeParse({ email });
if (!parsed.success) {
return { ok: false, error: 'Geçersiz e-posta adresi.' };
}
if (!(file instanceof File)) {
return { ok: false, error: 'Dosya bulunamadı.' };
}
const allowed = ['image/png', 'image/jpeg', 'application/pdf'];
if (!allowed.includes(file.type)) {
return { ok: false, error: 'İzin verilmeyen dosya türü.' };
}
const MAX = 5 * 1024 * 1024; // 5MB
if (file.size > MAX) {
return { ok: false, error: 'Dosya boyutu 5MB sınırını aşıyor.' };
}
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const filename =
crypto.randomBytes(8).toString('hex') + '-' + (file.name || 'upload');
const destDir = process.env.UPLOAD_DIR || '/tmp';
const destPath = path.join(destDir, filename);
await writeFile(destPath, buffer);
return { ok: true, path: destPath };
}
Form Bileşeni ve Kullanım
Formun action özelliğine doğrudan server action’ı vererek kolayca bağlayabiliriz. Bu sayede client tarafında ekstra fetch mantığına gerek kalmaz.
// app/page.tsx
import { uploadFile } from './actions';
export default function Page() {
return (
<main style={{ maxWidth: 560, margin: '40px auto', fontFamily: 'sans-serif' }}>
<h1>Dosya Yükleme Formu</h1>
<form action={uploadFile}>
<label>E-posta</label>
<input type="email" name="email" required placeholder="[email protected]" />
<label style={{ display: 'block', marginTop: 12 }}>Dosya (PNG/JPEG/PDF)</label>
<input type="file" name="file" accept="image/png,image/jpeg,application/pdf" required />
<button type="submit" style={{ marginTop: 16 }}>Gönder</button>
</form>
<p style={{ fontSize: 12, color: '#666', marginTop: 12 }}>
Not: Demo amaçlı yüklemeler geçici dizine kaydedilir.
</p>
</main>
);
}
İsterseniz kullanıcı deneyimini iyileştirmek için Client bileşeninde submit durumunu gösterebilir, form sonucunu Server Action’dan dönen nesneye göre ekrana yazdırabilirsiniz. Daha gelişmiş bir deneyim için useFormStatus ve useFormState yardımcılarını değerlendirebilirsiniz.
Güvenlik ve En İyi Uygulamalar
- Dosya türlerini beyaz listeyle sınırlandırın (PNG, JPEG, PDF gibi). Gerekirse MIME kontrolüne ek olarak sihirli bayt (magic bytes) kontrolü uygulayın.
- Boyut limiti belirleyin ve Next.js yapılandırmasında body limiti ile eşleştirin.
- Kalıcı depolama için S3 veya benzeri bir objeler deposu kullanın; dosya adlarını çakışmaları önlemek için hashleyin.
- İşlem süresi ve ortam kısıtları için Node.js runtime tercih edin; kenar (Edge) iş yüklerinde dosya manipülasyonu yerine imzalı URL yaklaşımı kullanın.
Çalıştırma ve Dağıtım
Geliştirme ortamında başlatmak için:
pnpm dev
Vercel’e dağıtırken ek bir API katmanına ihtiyaç duymadan aynı kod çalışır. Yalnızca kalıcı depolama kullanacaksanız ilgili servis kimlik bilgilerini çevresel değişkenlerle (ör. S3_ACCESS_KEY, S3_BUCKET) eklemeyi unutmayın.
Sonuç olarak, Next.js 14’te Server Actions ile güvenli ve performanslı bir dosya yükleme akışını, sade bir mimariyle hayata geçirebilirsiniz. Zod tabanlı doğrulama, sınırlı dosya türleri ve boyut kontrolleri ile riskleri minimize ederken, kullanıcıya yalın bir deneyim sunarsınız.
Hiç yorum yok:
Yorum Gönder