Modern Bir SaaS Geliştirmek: Next.js 14 ve AI ile CV Platformu Yolculuğumuz (Teknik Vaka Analizi)
İçindekiler
CV hazırlama araçlarının çoğu aynı kalıbı izler: Siz yazarsınız, uygulama her şeyi sunucuya gönderir ve CV'niz başkasının veritabanında yaşar. Biz farklı bir kısıtla başladık: Kullanıcı CV'leri asla sunucularımızda saklanmayacak. Bu yazı, bu kuraldan doğan teknik kararları anlatıyor. Sadece CV hazırlamak istiyorsanız: Hemen ücretsiz CV'nizi oluşturun.
Problem: Klasik Model Neden Yetmedi?
İstemci (React)
↓
API
↓
Veritabanı
↓
Kullanıcı CV'leri sunucuda saklanır
Gizlilik riski: Ad, telefon, e-posta ve iş geçmişinin tek bir yerde toplanması cazip bir hedef oluşturur.
Uyum yükü: Kişisel veri saklamak; saklama politikaları, silme talepleri ve KVKK/GDPR kapsamında dokümantasyon demektir.
Güven: "Güvenle saklıyoruz" bir vaattir. "Saklamıyoruz" ise doğrulanabilir.
Mimari Karar: Yerel Öncelikli (Local-First)
CV kullanıcının cihazında yaşar. Sunucu yalnızca tarayıcıda yapılamayan işleri üstlenir ve girdiyi saklamaz.
Tarayıcı
Zustand store (çalışma kopyası)
↓ gecikmeli, birleştirerek yazma
localStorage (kullanıcının tüm CV'leri)
↓ isteğe bağlı, kullanıcı başlatır
Kullanıcının kendi Google Drive'ı (drive.file izni)
Sunucu (CV verisi için durumsuz)
/api/pdf → PDF'i bellekte üretir, geri döner
/api/ai/* → seçilen metni yapay zeka sağlayıcısına iletir
Ödünleşimler ve çözümlerimiz:
Tarayıcı verisi silinirse CV'ler de silinir → kullanıcının kendi Drive'ına isteğe bağlı yedek ve indirmeden önce hatırlatma.
localStorage birkaç megabaytla sınırlı → profil fotoğrafları saklanmadan önce tarayıcıda küçültülür.
Cihazlar arası otomatik eşitleme yok → yine Drive yedeği, tamamen kullanıcının kontrolünde.
Teknoloji Yığını
Next.js 14 (App Router)
İçerik sayfaları (şablonlar, rehberler, blog) arama motorları için sunucuda render edilir. CV editörünün kendisi istemci tarafında çalışan bir uygulamadır. İhtiyaç duyulan birkaç sunucu işlevi route handler'larda bulunur.
Durum Yönetimi: Zustand + persist
Redux bu iş için fazla ağır olurdu. Tek bir Zustand store tüm CV'leri ve aktif CV'nin çalışma kopyasını tutar; persist middleware'i localStorage'a yazar.
// sadeleştirilmiş
const useResumeStore = create(
persist(
(set, get) => ({
resumes: {}, // id → saklanan CV
activeResumeId: null,
resumeData: emptyResume, // aktif CV'nin çalışma kopyası
updatePersonalInfo: (patch) => sync(set, get, patch),
}),
{ name: 'resume-storage', version: CURRENT_STORE_VERSION, migrate },
),
)
İki ayrıntı beklediğimizden önemli çıktı:
Sürümlü göçler (migration). CV'nin yapısı değiştiğinde store sürümü artırılır ve eski veriyi dönüştüren bir göç adımı eklenir. Aksi halde geri dönen kullanıcılar bir sonraki ziyarette CV'lerini kaybeder. Her göçün birim testi vardır.
Birleştiren bir depolama katmanı. İki sekme açıkken basit kalıcılıkta son yazan kazanır ve diğer sekmedeki değişiklikler sessizce kaybolur. Katmanımız yazmaları geciktirir, depodakiyle birleştirir, silmeleri süreli "mezar taşı" kayıtlarıyla izler ve daha yeni bir uygulama sürümünün yazdığı veriyi ezmez. Yazma hataları (ör. dolu kota) sessizce yutulmaz, kayıt göstergesinde görünür.
PDF Üretimi
Gerçek, seçilebilir metin içeren (ATS'lerin okuyabilmesi için) ve piksel düzeyinde doğru PDF'ler istiyorduk. İstemci tarafı canvas yaklaşımları metnin görüntüsünü üretir ve ilk şartı karşılamaz.
Çözüm: Tarayıcı önizleme HTML'ini bir route handler'a gönderir; sunucu bunu Playwright ile headless Chromium'da render eder ve PDF'i geri döndürür. Diske hiçbir şey yazılmaz.
// sadeleştirilmiş
const page = await browser.newPage()
await page.setContent(html, { waitUntil: 'networkidle' })
const pdf = await page.pdf({ format: 'A4', printBackground: true })
await page.close()
return new Response(pdf, { headers: { 'Content-Type': 'application/pdf' } })
Asıl zorluk çok sayfalı düzendi. Sayfa kenar boşlukları ve sayfa sonu kuralları iki yerde tanımlı: PDF route'unun baskı CSS'inde ve sayfa sonlarını taklit eden canlı önizlemede. Bir birim testi ikisini senkron tutar; bir betik de her şablon için sayfa boyutunu ve sayfa sayısını çalışan bir sunucuya karşı kontrol eder.
Yapay Zeka Entegrasyonu
Yapay zeka özellikleri (maddeleri yeniden yazma, beceri önerme, özet veya ön yazı taslağı, ilanla anahtar kelime karşılaştırma) Vercel AI SDK üzerinden Groq ile çalışır. Her AI route'u için bazı kurallar geçerli:
Model adı tek bir yerde. Sağlayıcılar modelleri emekliye ayırır; birden çok route'a sabit yazılmış bir model adı tüm AI özelliklerini aynı anda bozabilir. Model tek bir modülde tanımlıdır.
Uydurma yok. Her istem, sayı, tarih, işveren veya başarı uydurmayı yasaklayan ortak bir kural içerir.
Sınırlı girdi ve çıktı. İstek gövdesinin boyut sınırı vardır, kullanıcı metni istemden önce kırpılır, çıktı token sayısı sınırlıdır.
Güvenli loglama. SDK hataları istek gövdesini, yani kullanıcının CV metnini içerebilir. Hatalar yalnızca arındırılmış bir özet olarak loglanır.
Dürüst akış (streaming). Akışlı yanıtlar sağlayıcı hatalarını istemciye iletir; böylece bir hata boş ama "başarılı" bir yanıt gibi görünmez.
Hız sınırlama middleware'de yapılır, çünkü AI route'ları hesap gerektirmez.
Bu route'lar aldıkları metni işler ve hiçbir şey saklamaz.
Güvenlik Temelleri
Temizlenen zengin metin: CMS içeriği (blog yazıları, sayfalar) kaydedilirken DOMPurify ile, izin verilen etiket ve özniteliklerin açık listesiyle temizlenir.
Rota bazlı Content Security Policy: Yalnızca reklam gösterebilecek içerik sayfaları reklam sunucularına izin verir; editör ve diğer tüm sayfalar bunları engeller.
Yönetici oturumları httpOnly çerezde taşınan imzalı JWT'lerdir, middleware'de doğrulanır ve isteğe bağlı TOTP iki aşamalı doğrulama ile korunabilir.
Öğrendiklerimiz
1. Local-first bu kullanım için gerçekten işe yarıyor
CV, tek bir kişinin düzenlediği küçük ve kişisel bir belgedir. Yerel öncelikli mimariye doğal olarak uyar. Her şeyi merkezde saklayan birçok uygulamanın buna aslında ihtiyacı yok.
2. Zor kısım kalıcılık
Birden çok sekme, şema değişiklikleri, depolama kotaları: Asıl mühendislik emeği arayüze değil, yerel depolamayı güvenilir kılmaya gitti.
3. Gizlilik doğrulanabilir olmalı
Herkes tarayıcısının Ağ (Network) sekmesini açıp editörün ne gönderdiğini izleyebilir. Bu, herhangi bir pazarlama cümlesinden daha güçlü bir argüman.
4. Yapay zekanın istemden fazlasına, korkuluklara ihtiyacı var
Uydurma yasağı, girdi sınırları, güvenli loglama ve tek bir model yapılandırması en can sıkıcı hata türlerini önlüyor.
5. Yayınla, sonra geliştir
Drive yedeği ve çok sayfalı PDF gibi özellikler, gerçek kullanım ihtiyaç olduğunu gösterdikten sonra geldi.
Sonuç
"Verilerinizi koruyoruz" bir vaattir. "Verileriniz bizde yok" ise bir mimari. Gizlilik öncelikli bir ürün geliştiriyorsanız, yerel öncelikli durum yönetimi, durumsuz sunucu işlevleri ve doğrulanabilir davranış sizi çok ileri taşır. CV editörünü deneyin - artık arkasında ne olduğunu biliyorsunuz.
İlgili rehberler ve kaynaklar
- Gizlilik politikası: Cvbuildly hangi veriyi nerede ve ne kadar süre işler
- Hakkımızda: aracı neden yerel öncelikli kurduğumuz
- Next.js belgeleri: uygulamanın üzerine kurulduğu çatının resmî belgeleri (İngilizce)
- MDN: Window.localStorage: tarayıcı yerel deposunun nasıl çalıştığı (İngilizce)
- Google Drive API: kapsamlar: drive.file kapsamının yalnızca uygulamanın oluşturduğu dosyalara erişmesi (İngilizce)
CV'ni şimdi oluştur
Ücretsiz, kayıt gerektirmez; verilerin yalnızca tarayıcında kalır.