Next.js 15 ile Veritabanısız Headless CMS Mimarisi: GitHub REST API Commit Köprüsü ve On-Demand ISR
Web MimarisiNext.jsArchitecture

Next.js 15 ile Veritabanısız Headless CMS Mimarisi: GitHub REST API Commit Köprüsü ve On-Demand ISR

calendar_today19 AĞU 2026
schedule5 DK OKUMA
boltİLERİ SEVİYE

Next.js 15 ile Veritabanısız Headless CMS Mimarisi: GitHub REST API Commit Köprüsü ve On-Demand ISR

Kişisel portfolyosunu veya teknik günlüğünü yöneten bir yazılım mühendisi için içerik yönetim sistemi (CMS) kurmak genellikle üç temel açmazla sonuçlanır:

  1. Geleneksel İlişkisel/NoSQL Veritabanları (PostgreSQL, MySQL, MongoDB): Sunucusuz (serverless) edge ağlarında soğuk başlangıç (cold-start) gecikmeleri, bağlantı havuzu yönetimi ve her ay düzenli sunucu barındırma maliyeti yaratır.
  2. Üçüncü Parti Headless CMS Servisleri (Sanity, Strapi, Contentful): Dış API bağımlılığı, kota sınırları, servis sağlayıcıya kilitlenme (vendor lock-in) ve frontend tarafında ekstra istemci paket yükü getirir.
  3. Yerel Markdown Dosyaları (content/posts/*.md): Sıfır maliyetlidir, Git versiyon kontrolüne sahiptir ve son derece hızlıdır; fakat Vercel gibi sunucusuz ortamlarda kritik bir kısıtla karşılaşır: çalışma zamanında disk salt-okunur (read-only) olduğu için bir admin panelinden doğrudan yeni dosya yazılamaz.

Bu yazıda, Next.js 15 App Router içerisinde harici bir veritabanına gerek kalmadan geliştirdiğimiz Veritabanısız Headless CMS mimarisini, GitHub REST API Commit Köprüsü ve On-Demand ISR (Incremental Static Regeneration) mekanizmalarını adım adım inceliyoruz.


1. Karşılaşılan Temel Mimari Problem

Next.js uygulamasını Vercel veya benzeri bir sunucusuz platformda barındırdığınızda, uygulamanız stateless (durumsuz) Lambda fonksiyonları üzerinde çalışır.

Yerel geliştirme ortamında (npm run dev) Node.js fs.writeFileSync fonksiyonu ile content/posts/yazi.md dosyasını diske yazdığınızda Next.js bunu anında algılar. Ancak sunucusuz üretim (production) ortamında disk salt okunurdur. Lambda sonlandığında bellekteki tüm geçici durumlar yok olur.

Dolayısıyla bir admin panelinden "Yayınla" dendiğinde dosya canlı sunucuya fiziksel olarak yazılamaz; içerik ancak biri bilgisayarından git commit ve git push yaparsa güncellenir.

Bu süreci tamamen otomatikleştirmek için Git deposunun kendisini bir veritabanı gibi kullanan bir köprü tasarladık.


2. Tasarlanan Çözüm Mimarisi

Sistem, mobil ve masaüstü tüm ekranlarda kusursuz çalışan 4 adımlı bir veri hattı ile işler:

Adım 1: Yönetim Arayüzü (/admin)

Yazar, Next.js içerisine entegre edilmiş yönetim panelinde içeriği Markdown formatında hazırlar, kapak görselini ve etiketlerini belirler.

Adım 2: Sunucusuz Doğrulama (/api/admin/blog)

Form gönderildiğinde Next.js App Router API rotası tetiklenir. API, başlığı ve içeriği kontrol eder; slug alanını regex ile temizleyerek olası dizin atlama (Path Traversal) açıklarını engeller.

Adım 3: İkili Senkronizasyon (Local & GitHub REST API)

  • Yerel Geliştirmede: content/posts/<slug>.md dosyası doğrudan yerel dosya sistemine yazılır.
  • Canlı Ortamda (Production): GitHub Git Data API uç noktalarına bağlanılarak yeni dosya blob olarak eklenir, commit üretilir ve main dalı otomatik güncellenir.

Adım 4: Anında Önbellek Yenileme (On-Demand ISR)

Commit tamamlandığı anda sunucusuz rotadan revalidatePath('/blog') ve revalidatePath('/blog/[slug]') çağrılır. Vercel CDN üzerindeki önbellek milisaniyeler içinde tazelenir ve yazı tüm dünyada anında yayına girer.


3. GitHub REST API Commit Servisinin Kodlanması

Aşağıda, harici bir Git CLI komutuna ihtiyaç duymadan doğrudan GitHub REST API üzerinden dosya commit eden servis fonksiyonumuz yer almaktadır:

interface GitHubCommitFile {
  path: string;
  content: string;
}

export async function commitFilesToGitHub(
  files: GitHubCommitFile[],
  commitMessage: string
): Promise<{ success: boolean; message: string; commitSha?: string }> {
  const token = process.env.GITHUB_TOKEN;
  const owner = process.env.GITHUB_REPO_OWNER || 'sandrotonal';
  const repo = process.env.GITHUB_REPO_NAME || 'gucluyumheqoder';
  const branch = process.env.GITHUB_BRANCH || 'main';

  if (!token) {
    return { success: false, message: 'Yerel mod: GITHUB_TOKEN tanımlı değil.' };
  }

  const headers = {
    Authorization: `Bearer ${token}`,
    Accept: 'application/vnd.github.v3+json',
    'Content-Type': 'application/json',
    'User-Agent': 'gucluyumhe-cms',
  };

  // 1. Hedef daldaki son commit SHA değerini al
  const refRes = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/git/ref/heads/${branch}`,
    { headers }
  );
  const refData = await refRes.json();
  const latestCommitSha = refData.object.sha;

  // 2. Base Tree SHA değerini öğren
  const commitRes = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/git/commits/${latestCommitSha}`,
    { headers }
  );
  const commitData = await commitRes.json();
  const baseTreeSha = commitData.tree.sha;

  // 3. Dosyaları tree girdilerine dönüştür
  const treeEntries = files.map((file) => ({
    path: file.path.replace(/\\/g, '/').replace(/^\//, ''),
    mode: '100644',
    type: 'blob',
    content: file.content,
  }));

  const treeRes = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/git/trees`,
    {
      method: 'POST',
      headers,
      body: JSON.stringify({ base_tree: baseTreeSha, tree: treeEntries }),
    }
  );
  const treeData = await treeRes.json();

  // 4. Yeni commit nesnesini oluştur
  const newCommitRes = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/git/commits`,
    {
      method: 'POST',
      headers,
      body: JSON.stringify({
        message: commitMessage,
        tree: treeData.sha,
        parents: [latestCommitSha],
      }),
    }
  );
  const newCommitData = await newCommitRes.json();

  // 5. Dal referansını yeni commit SHA ile güncelle
  await fetch(
    `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
    {
      method: 'PATCH',
      headers,
      body: JSON.stringify({ sha: newCommitData.sha, force: false }),
    }
  );

  return { success: true, message: 'Commit tamamlandı.', commitSha: newCommitData.sha };
}

4. Medium Canlı Akışı ve Tek Tıkla İçe Aktarma

İçerik üretim sürecini hızlandırmak için Medium üzerindeki yayınlarımızı @gucluyumhe RSS beslemesinden canlı olarak çeken bir modül geliştirdik:

  • RSS içeriğindeki takip pikselleri ve gereksiz HTML etiketleri temizlenir.
  • "Import to Blog" butonuna tıklandığında yazının başlığı, kapak görseli, özeti ve kaynak bağlantısı otomatik olarak Markdown editörüne yüklenir.
  • Editör üzerinden ek teknik detaylar girilerek tek tıkla doğrudan portfolyoda da yayına alınabilir.

5. Mimari Avantajlar ve Performans Prensipleri

Bu yaklaşımın geliştirici portfolyosuna sağladığı somut faydalar:

  • Sıfır İstemci Yükü: Admin paneline ait tüm form, ikon ve editör paketleri /admin rotasında izole edilmiştir; anasayfa ve blog sayfalarına fazladan 1 bayt dahi yük bindirmez.
  • Statik Ön Derleme Gücü (SSG): Tüm blog yazıları derleme anında statik HTML'e dönüştürülür ve CDN üzerinde barındırılır.
  • Güvenlik ve İzolasyon: Harici bir veritabanı açık portu veya SQL injection riski bulunmaz. Veriler Git üzerinde şeffaf ve sürümlenmiş olarak saklanır.
  • Sıfır Ek Maliyet: Harici veritabanı abonelikleri veya Headless CMS kotaları olmadan tamamen ücretsiz çalışır.

6. Sonuç ve Çıkarımlar

Geliştirici portfolyoları ve teknik bloglar için Git deposunu tek ve değiştirilemez veri kaynağı (single source of truth) olarak kullanmak; hem veri sahipliğini korur hem de bakım maliyetini sıfıra indirir. Next.js 15'in On-Demand ISR yetenekleri ve GitHub REST API birleştiğinde, geleneksel veritabanlarının karmaşıklığına girmeden tam teşekküllü bir CMS deneyimi elde etmek mümkündür.

Ömer Özbay
YAZAN

Ömer Özbay

Yüksek performanslı arka yüz mimarilerini piksel hassasiyetinde ön yüz deneyimleriyle birleştirmede uzmanlaşmış Tam Yığın Geliştirici. Yapay zeka ve modern web teknolojileriyle geleceği inşa ediyor.

Next.js 15 ile Veritabanısız Headless CMS Mimarisi: GitHub REST API Commit Köprüsü ve On-Demand ISR | Ömer Özbay Günlük | Ömer Özbay