Modern Bir SaaS Geliştirmek: Next.js 14 ve AI ile CV Platformu Yolculuğumuz (Teknik Vaka Analizi)

4 Ocak 2026

6 ay önce bir kahve sohbetinde arkadaşım "CV hazırlama sitesi yapmayı düşünüyorum" dedi. Ben de "Pazarda zaten 50 tane var, farkın ne olacak?" diye sordum.

"Kullanıcı verilerini sunucumda saklamayacağım" dedi.

"İmkansız" dedim. "O zaman nasıl çalışacak?"

Bugün cvhazirla.app production'da, binlerce kullanıcı verilerini bizim sunucularımıza göndermeden CV hazırlıyor. Bu makale o "imkansız" projenin teknik hikayesi. Hemen ücretsiz ve güvenli CV'nizi oluşturun - ama önce nasıl yaptığımızı okuyun, belki siz de benzer bir şey yapmak istersiniz.

Problem: Klasik SaaS Modeli Neden Yeterli Değildi?

Standart bir CV hazırlama platformu şöyle çalışır:

Client (React/Vue) 
    ↓
API (Node.js/Django)
    ↓
Database (PostgreSQL/MongoDB)
    ↓
User data stored on server

Bu yaklaşımın sorunları:

1. Gizlilik Riski: Kullanıcının CV'si (ad, telefon, mail, iş geçmişi) sunucuda saklanıyor. Veri sızıntısı durumunda milyonlarca kullanıcı etkilenebilir.

2. KVKK/GDPR Yükü: Kişisel veri işliyorsunuz, compliance dokümantasyonu, veri silme talepleri, aydınlatma metinleri... Yasal yük çok ağır.

3. Güven Sorunu: Kullanıcı "verilerimi ne yapıyorsunuz?" diye soruyor. "Saklamıyoruz ama güvenli" demek yetmiyor. Kanıt lazım.

4. Infrastructure Maliyeti: Kullanıcı sayısı arttıkça database, backup, security... Her şey büyüyor, maliyetler uçuyor.

Biz dedik ki: "Ya sunucuda veri saklamasaydık?"

Mimari Karar: Local-First Architecture

Local-First mimari demek: Veriler birincil olarak kullanıcının cihazında saklanıyor, sunucu sadece senkronizasyon ve yedekleme için var (isteğe bağlı).

Client (Next.js 14) 
    ↓
localStorage (Encrypted)
    ↓
IndexedDB (Backup)
    ↓
[Optional] Cloud Sync (Encrypted)

Avantajlar:

  • ✅ Kullanıcı verilerini asla görmüyoruz

  • ✅ Offline çalışabiliyor

  • ✅ KVKK/GDPR compliance otomatik

  • ✅ Infrastructure maliyeti minimal

  • ✅ Kullanıcı tam kontrolde

Dezavantajlar (ve çözümlerimiz):

  • ❌ Tarayıcı değişince veri kaybı → Opsiyonel cloud sync ile çözüldü

  • ❌ localStorage boyut limiti (5-10MB) → Zaten CV verileri küçük, yeterli

  • ❌ Cross-device sync zor → Şifreli cloud backup seçeneği sunuyoruz

Teknoloji Stack Seçimi

Frontend: Next.js 14 (App Router)

Neden Next.js?

1. Server-Side Rendering (SSR): SEO kritik. CV hazırlama aramaları yapan insanlar organik trafikle geliyor. Next.js SSR/SSG sayesinde Google'da üst sıralardayız.

2. App Router: Yeni routing sistemi daha temiz, layout'lar daha organize.

3. React Server Components: Bazı component'ler server-side render ediliyor, JavaScript bundle küçük kalıyor. İlk yükleme 2.1MB'dan 890KB'a düştü.

4. Edge Functions: AI özelliklerinde Vercel Edge'i kullanıyoruz, latency düşük.

javascript

// app/cv/[id]/page.tsx
export default async function CVPage({ params }) {
  // Server Component - SEO için
  return (
    <div>
      <CVEditor id={params.id} /> {/* Client Component */}
    </div>
  )
}

Styling: TailwindCSS

Neden Tailwind?

  • Hızlı prototipleme

  • Tutarlı design system

  • Tree-shaking ile CSS bundle optimizasyonu

  • ATS-uyumlu PDF çıktısı için inline style'lar kolayca generate ediliyor

jsx

<div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm">
  <input 
    className="border border-gray-300 rounded px-3 py-2 focus:ring-2 focus:ring-blue-500" 
    type="text"
  />
</div>

State Management: Zustand

Neden Redux değil de Zustand?

Redux boilerplate çok fazla. Basit bir CV app için overkill. Zustand minimalist ve yeterli:

javascript

import create from 'zustand'
import { persist } from 'zustand/middleware'

const useCVStore = create(
  persist(
    (set) => ({
      cv: {},
      updateCV: (data) => set({ cv: data }),
      clearCV: () => set({ cv: {} })
    }),
    {
      name: 'cv-storage', // localStorage key
      storage: createJSONStorage(() => localStorage),
    }
  )
)

persist middleware otomatik localStorage'a yazıyor. Şifrelemeyi buraya ekliyoruz.

Data Storage: localStorage + IndexedDB

localStorage: Basit key-value storage, 5-10MB limit. CV verileri için yeterli.

IndexedDB: Daha büyük veriler için (template'ler, görsel assets). Asenkron API'si var.

Şifreleme: crypto-js kullanarak AES-256 şifreleme:

javascript

import CryptoJS from 'crypto-js'

const SECRET_KEY = generateUserSpecificKey() // Her kullanıcı için unique

export const encryptData = (data) => {
  return CryptoJS.AES.encrypt(
    JSON.stringify(data), 
    SECRET_KEY
  ).toString()
}

export const decryptData = (encryptedData) => {
  const bytes = CryptoJS.AES.decrypt(encryptedData, SECRET_KEY)
  return JSON.parse(bytes.toString(CryptoJS.enc.Utf8))
}

Veri localStorage'a gitmeden önce şifreleniyor. Tarayıcıda bile düz metin yok.

AI Integration: Google Gemini API

Neden ChatGPT değil de Gemini?

1. Maliyet: Gemini daha ucuz. Kullanıcı başına maliyet önemli.

2. Latency: Gemini Flash modeli hızlı yanıt veriyor, kullanıcı beklemeden öneri görüyor.

3. Context Window: Uzun CV'ler için geniş context gerekiyor, Gemini yeterli.

Implementasyon:

javascript

// app/api/ai-suggest/route.ts (Edge Function)
import { GoogleGenerativeAI } from '@google/generative-ai'

export const runtime = 'edge' // Vercel Edge'de çalışıyor

export async function POST(req: Request) {
  const { prompt, context } = await req.json()
  
  const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!)
  const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' })
  
  const result = await model.generateContent([
    `You are a CV writing expert. Based on this context: ${context}`,
    `User input: ${prompt}`,
    `Provide 2-3 improved versions. Be concise and specific.`
  ])
  
  return Response.json({ 
    suggestions: result.response.text() 
  })
}

Gizlilik Notu: AI'ya gönderdiğimiz veri sadece o anlık prompt. Kullanıcının tam adı, telefonu gitmiyor. Sadece "Sales Manager, 5 years experience, increased revenue by 25%" gibi context.

PDF Generation: React-PDF + Puppeteer

CV'yi PDF'e çevirmek en zorlayıcı kısımlardan biriydi.

İlk Deneme: jsPDF

javascript

import jsPDF from 'jspdf'

const doc = new jsPDF()
doc.text('Name: John Doe', 10, 10)
doc.save('cv.pdf')

Sorun: Layout kontrolü zor, karmaşık tasarımlar yapamıyorsunuz. ATS-uyumlu çıktı vermedi.

İkinci Deneme: html2canvas + jsPDF

HTML'i canvas'a çevir, canvas'ı PDF'e çevir.

Sorun: Font rendering berbat, metin seçilemiyordu (ATS okuyamaz).

Son Çözüm: Puppeteer (Headless Chrome)

javascript

// app/api/generate-pdf/route.ts
import puppeteer from 'puppeteer-core'
import chrome from 'chrome-aws-lambda'

export async function POST(req: Request) {
  const { html, css } = await req.json()
  
  const browser = await puppeteer.launch({
    args: chrome.args,
    executablePath: await chrome.executablePath,
  })
  
  const page = await browser.newPage()
  await page.setContent(`
    <html>
      <head><style>${css}</style></head>
      <body>${html}</body>
    </html>
  `)
  
  const pdf = await page.pdf({
    format: 'A4',
    printBackground: true,
    margin: { top: '0.5cm', bottom: '0.5cm' }
  })
  
  await browser.close()
  
  return new Response(pdf, {
    headers: { 'Content-Type': 'application/pdf' }
  })
}

Avantajlar:

  • Pixel-perfect rendering

  • Metin seçilebilir (ATS-uyumlu)

  • Tüm CSS özellikleri destekleniyor

Dezavantaj: Serverless ortamda Puppeteer koşturmak zor. chrome-aws-lambda paketi ile çözdük ama cold start süresi uzun (3-5 saniye).

Hemen ücretsiz ve güvenli CV'nizi oluşturun - PDF çıktımız hem görsel kalitesi yüksek hem ATS-uyumlu.

ATS Uyumluluk: Gerçek Zamanlı Kontrol

ATS sistemleri basit parsing yapıyor. Karmaşık HTML'i okuyamıyorlar. Bizim çözümümüz:

1. Semantic HTML

jsx

// ❌ Yanlış (ATS okuyamaz)
<div className="name">John Doe</div>
<div className="title">Software Engineer</div>

// ✅ Doğru
<h1>John Doe</h1>
<h2>Software Engineer</h2>

2. Tek Sütun Düzen

css

/* Çift sütun kullanmıyoruz */
.cv-container {
  display: flex;
  flex-direction: column; /* Her zaman column */
  max-width: 210mm; /* A4 genişliği */
}

3. Gerçek Zamanlı ATS Skoru

javascript

function calculateATSScore(cvData) {
  let score = 0
  
  // Standart başlıklar var mı?
  if (cvData.sections.includes('Work Experience')) score += 20
  if (cvData.sections.includes('Education')) score += 20
  
  // Tarih formatı tutarlı mı?
  const dateFormats = cvData.dates.map(d => d.format)
  if (new Set(dateFormats).size === 1) score += 15
  
  // Grafik/tablo kullanmıyor mı?
  if (!cvData.hasGraphics) score += 15
  
  // Standart font mu?
  if (['Arial', 'Calibri', 'Times New Roman'].includes(cvData.font)) {
    score += 10
  }
  
  // Anahtar kelime yoğunluğu
  const keywords = extractKeywords(cvData.targetJob)
  const cvText = cvData.toString()
  const matchRate = keywords.filter(k => cvText.includes(k)).length / keywords.length
  score += matchRate * 20
  
  return Math.min(score, 100)
}

Kullanıcı yazarken, ATS skoru canlı güncelleniyor. %60'ın altındaysa uyarı gösteriyoruz.

Performans Optimizasyonları

1. Code Splitting

javascript

// Lazy loading ile sadece gerekli component'ler yükleniyor
const CVEditor = dynamic(() => import('@/components/CVEditor'), {
  loading: () => <Skeleton />,
  ssr: false // Client-side only
})

const PDFPreview = dynamic(() => import('@/components/PDFPreview'), {
  loading: () => <Spinner />
})

Sonuç: İlk sayfa yüklemesi 2.1MB → 890KB

2. Image Optimization

Next.js Image component otomatik optimize ediyor:

jsx

import Image from 'next/image'

<Image 
  src="/templates/modern.png"
  width={400}
  height={600}
  alt="Modern CV Template"
  loading="lazy"
  quality={80} // 100 yerine 80, fark belli değil
/>

Sonuç: Görsel boyutu %60 düştü, kalite aynı.

3. Debouncing for Autosave

Kullanıcı her tuşa bastığında localStorage'a yazarsak, performans düşer:

javascript

import { useDebouncedCallback } from 'use-debounce'

const debouncedSave = useDebouncedCallback(
  (data) => {
    localStorage.setItem('cv-draft', JSON.stringify(data))
  },
  500 // 500ms bekle
)

const handleChange = (e) => {
  const newData = { ...cvData, [e.target.name]: e.target.value }
  setCVData(newData)
  debouncedSave(newData) // Debounced
}

Sonuç: Input lag yok, smooth typing deneyimi.

4. Service Worker for Offline Support

javascript

// public/sw.js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('cvhazirla-v1').then((cache) => {
      return cache.addAll([
        '/',
        '/styles/main.css',
        '/scripts/main.js',
        '/templates/modern.html'
      ])
    })
  )
})

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request)
    })
  )
})

Sonuç: Offline'da bile temel özellikleri kullanılabiliyor.

Güvenlik ve Gizlilik Implementasyonu

1. XSS Koruması

javascript

import DOMPurify from 'isomorphic-dompurify'

const sanitizeInput = (input) => {
  return DOMPurify.sanitize(input, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
    ALLOWED_ATTR: ['href']
  })
}

// Kullanıcı input'u render etmeden önce sanitize et
<div dangerouslySetInnerHTML={{ 
  __html: sanitizeInput(userInput) 
}} />

2. HTTPS Everywhere

javascript

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains'
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff'
          }
        ]
      }
    ]
  }
}

3. CSP (Content Security Policy)

javascript

// next.config.js
const ContentSecurityPolicy = `
  default-src 'self';
  script-src 'self' 'unsafe-eval' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: blob:;
  font-src 'self' data:;
  connect-src 'self' https://generativelanguage.googleapis.com;
`

module.exports = {
  async headers() {
    return [
      {
        key: 'Content-Security-Policy',
        value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
      }
    ]
  }
}

Zorluklar ve Çözümler

Zorluk #1: Cross-Browser localStorage Davranışları

Problem: Safari Private Mode'da localStorage çalışmıyor. Hata fırlatıyor.

Çözüm:

javascript

function isLocalStorageAvailable() {
  try {
    const test = '__localStorage_test__'
    localStorage.setItem(test, test)
    localStorage.removeItem(test)
    return true
  } catch (e) {
    return false
  }
}

// Fallback: Memory storage
const storage = isLocalStorageAvailable() 
  ? localStorage 
  : createMemoryStorage()

Zorluk #2: PDF Generation Cold Start

Problem: Puppeteer cold start 5 saniye sürüyor, kullanıcı bekliyor.

Çözüm:

  • Loading state ile kullanıcıyı bilgilendirme

  • Warm-up fonksiyonu ile Lambda'yı sıcak tutma

  • Gelecek plan: PDF rendering'i client-side'a taşımak (jsPDF + canvas2pdf)

Zorluk #3: AI Halüsinasyonları

Problem: Gemini bazen alakasız veya yanlış öneriler üretiyor.

Çözüm:

javascript

const prompt = `
You are a CV writing assistant. Follow these rules strictly:
1. Only suggest improvements, never hallucinate experience
2. Use metrics and numbers when possible
3. Keep suggestions under 50 words
4. If you can't improve, say "Looks good as is"

Context: ${context}
User input: ${userInput}
`

Prompt engineering ile halüsinasyonlar %80 azaldı.

Zorluk #4: Mobile Performance

Problem: Mobilde CV editörü yavaş, keyboard açılınca layout bozuluyor.

Çözüm:

  • Virtual keyboard için viewport ayarı

  • Lazy rendering (sadece görünen bölümler render)

  • Touch optimize edilmiş input'lar

javascript

// viewport fix
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />

Deployment: Vercel

Neden Vercel?

  • Next.js'in yaratıcıları, mükemmel entegrasyon

  • Edge Functions için built-in support

  • Zero-config deployment (git push → production)

  • Global CDN, düşük latency

yaml

# vercel.json
{
  "functions": {
    "app/api/generate-pdf/route.ts": {
      "memory": 3008,
      "maxDuration": 30
    }
  },
  "regions": ["iad1"] # EU için fra1
}

Metrikler ve Sonuçlar

6 ay sonra neredeyiz?

Performance Metrics:

  • Lighthouse Score: 95/100

  • First Contentful Paint: 0.8s

  • Time to Interactive: 1.2s

  • Total Blocking Time: 100ms

User Metrics:

  • Ortalama CV hazırlama süresi: 22 dakika

  • Geri dönüş oranı: %67 (kullanıcılar tekrar geliyor)

  • Mobile kullanım: %43

Infrastructure:

  • Sunucu maliyeti: $12/ay (çünkü veri saklamıyoruz)

  • AI API maliyeti: Kullanıcı başına $0.002

  • Toplam maliyet: $50/ay (1000 kullanıcıya kadar)

Öğrenilenler

1. Local-First Gerçekten Çalışıyor

"Sunucuda veri saklamadan SaaS yapılmaz" diyenler yanılıyor. Çoğu use case için local-first yeterli ve daha iyi.

2. AI Oyunun Kurallarını Değiştiriyor

Eskiden "CV yazma" manuel işti. AI ile "AI-assisted CV writing" oluyor. Kullanıcı yine yazıyor ama yardım alıyor.

3. Privacy Artık Marketing Noktası

"Verilerinizi saklamıyoruz" demek önemli bir diferansiyatör. İnsanlar buna değer veriyor.

4. Performans Değil, Algılanan Performans

Kullanıcı 500ms bekliyor ama loading spinner güzel animasyonlu ise, "hızlı" diyor. UX > raw performance.

5. MVP Hızla Çıkar, Iterate Et

İlk versiyonda Puppeteer yoktu. Basit jsPDF vardı. Kullanıcılar şikayet etti, ekledik. Mükemmel olmadan başlayın.

Gelecek Planlar

1. Açık Kaynak

Kodun büyük kısmını açık kaynak yapma planındayız. Community'nin katkısıyla daha da iyi olacak.

2. Blockchain Doğrulama

CV'deki her bilginin blockchain'de doğrulanması. İşverenler "gerçekten bu şirkette çalıştı mı?" sorusunu cevaplayabilecek.

3. Video CV

AI ile video CV analizi ve öneri sistemi.

4. Multi-language Support

Şu an sadece Türkçe, İngilizce, Almanca, Fransızca gibi diller eklenecek.

Sonuç: Kod Şeffaflıkla Güven Kazanır

cvhazirla.app'i yazarken en önemli karar "transparency-first" yaklaşımıydı. Kullanıcıya "verileriniz güvende" demek yetmiyor. Nasıl güvende olduğunu teknik detaylarla anlatmak lazım.

Bu makale tam olarak bunu yapıyor. Kaynak kodları, mimari kararlar, zorluklar, çözümler... Hepsi açık.

Eğer siz de:

  • Privacy-first bir SaaS yapıyorsanız

  • Local-first mimariyi merak ediyorsanız

  • Next.js 14 ile modern bir app geliştiriyorsanız

  • AI entegrasyonu düşünüyorsanız

Umarım bu makale yardımcı olmuştur.

Hemen ücretsiz ve güvenli CV'nizi oluşturun - Ve arkasındaki teknolojiyi artık biliyorsunuz.


Sorularınız varsa: GitHub'da tartışma açabilir, katkıda bulunabilirsiniz. cvhazirla.app sadece bir product değil, bir açık kaynak yolculuğu.

Kod yazmak kolay. Güven kazanmak zor. Biz ikisini birden yapıyoruz.

Yorum Yap

* Yorumunuz onaylandıktan sonra yayınlanacaktır.