Ömer
Özbay

Full-Stack Engineer

LOADING
©2026
Portfolyo Sitesinde Uzun Vadede Fark Yaratan Mimari Kararlar
MİMARİMimariNext.js

Portfolyo Sitesinde Uzun Vadede Fark Yaratan Mimari Kararlar

calendar_today13 TEM 2026
schedule8 DK OKUMA
boltİLERİ SEVİYE

Portfolyo Sitesinde Uzun Vadede Fark Yaratan Mimari Kararlar

Çoğu portfolyo sitesi tek bir an için inşa edilir: iş başvurusu veya müşteri sunumu. Geliştiriciler bir şeyi hayata geçirmek için aceleyle öğrendikleri son framework'ü kullanır ve gönderir. Altı ay sonra ise yeni bir bölüm eklemek istediklerinde kendi kodlarıyla boğuşuyorlar.

gucluyumhe.dev'in farklı olmasını istedim. Bugün iyi görünen bir portfolyo değil, minimum bakım yükü ile zamanla iyileşen bir teknik platform.

İşte en büyük uzun vadeli farkı yaratan 6 mimari karar — ve akıllıca görünen ama sürtünme yaratan 2 karar.


Karar 1: İçerik için Tek Gerçek Kaynak Olarak Markdown

Cazip Olan: CMS veya Veritabanı Kullanmak

Pek çok geliştirici blog içeriği için içgüdüsel olarak headless bir CMS'e (Contentful, Sanity, Notion API) veya veritabanına (Supabase, PlanetScale) uzanır. Bunlar ölçeklenebilir ve production'a hazır hissettiriyor.

Tek yazarlı bir portfolyo blogu için bu muazzam bir aşırı mühendisliktir.

Bunun Yerine Ne Yaptım: Dosya Sistemi Markdown

Her blog yazısı /content/posts/ içinde bir .md dosyası. Slug dosya adı, metadata frontmatter. Tüm içerik kataloğu, klonlayabileceğiniz ve herhangi bir metin editöründe açabileceğiniz bir dizin.

// src/utils/markdown.ts
import fs from 'fs/promises';
import path from 'path';
import matter from 'gray-matter';
import { remark } from 'remark';
import remarkHtml from 'remark-html';

export interface PostFrontmatter {
  title: string;
  date: string;
  author: string;
  excerpt: string;
  coverImage: string;
  tags: string[];
  category: string;
  locale: 'en' | 'tr';
}

export interface PostData extends PostFrontmatter {
  slug: string;
  contentHtml: string;
  readingTimeMinutes: number;
}

const POSTS_DIRECTORY = path.join(process.cwd(), 'content/posts');

export async function getPostBySlug(slug: string): Promise<PostData | null> {
  try {
    const fullPath = path.join(POSTS_DIRECTORY, `${slug}.md`);
    const fileContents = await fs.readFile(fullPath, 'utf8');
    const { data, content } = matter(fileContents);

    const processedContent = await remark()
      .use(remarkHtml, { sanitize: false })
      .process(content);

    const wordCount = content.split(/\s+/).length;

    return {
      slug,
      ...(data as PostFrontmatter),
      contentHtml: processedContent.toString(),
      readingTimeMinutes: Math.ceil(wordCount / 200)
    };
  } catch {
    return null;
  }
}

Neden Uzun Vadede Kazandı?

  • Sıfır bağımlılık: CMS aboneliği yok, API rate limiti yok, vendor bağımlılığı yok
  • Git geçmişi changelog'unuz: Her düzenleme yazar ve zaman damgasıyla version-controlled
  • Offline-first geliştirme: İnternetsiz, herhangi bir editörde yazı yazın
  • Build-time rendering: Markdown build anında ayrıştırılır — sıfır runtime yükü
  • Konvansiyonla çok dilli: post.md (İngilizce) ve post_tr.md (Türkçe), dosya adından dil tespiti

Üç yıl sonra Next.js'ten Astro veya SvelteKit'e geçersem, tüm içerik katalogum sıfır değişiklikle migrate oluyor. Markdown dosyaları framework-agnostik.


Karar 2: Çeviri Kütüphanesi Olmadan Locale Tabanlı Routing

Cazip Olan: next-i18next veya next-intl

Çoğu çok dilli Next.js tutorial'ı hemen next-i18next veya next-intl'e uzanır. Bunlar güçlü kütüphaneler — ama basit bir kullanım durumu için karmaşıklık, konfigürasyon dosyaları ve anlaşılması zor bir mental model ekliyor.

Ne Yaptım: Konvansiyon Tabanlı Routing

// app/[locale]/blog/[slug]/page.tsx

export async function generateStaticParams() {
  const allFiles = await fs.readdir(POSTS_DIRECTORY);

  return allFiles
    .filter(file => file.endsWith('.md'))
    .map(file => {
      const isTranslation = file.endsWith('_tr.md');
      const locale = isTranslation ? 'tr' : 'en';
      const slug = file
        .replace('_tr.md', '')
        .replace('.md', '');

      return { locale, slug };
    });
}
content/posts/
  building-an-ai-agent-with-mcp.md     adresi:  /en/blog/building-an-ai-agent-with-mcp
  building-an-ai-agent-with-mcp_tr.md  adresi:  /tr/blog/building-an-ai-agent-with-mcp
  nextjs-performance-before-seo.md     adresi:  /en/blog/nextjs-performance-before-seo
  nextjs-performance-before-seo_tr.md  adresi:  /tr/blog/nextjs-performance-before-seo

Bu karar bir bağımlılığın tamamını ve yüzlerce satır konfigürasyonu ortadan kaldırdı. Yeni bir dil eklemek _de.md suffix'li dosyalar eklemek demek — kütüphane konfigürasyon değişikliği yok.


Karar 3: Her Sayfa Türü için JSON-LD Structured Data

Cazip Olan: Atlamak — Arama Motorları Halleder

Çoğu geliştirici JSON-LD'yi ertelenmiş sonuçlarla ekstra SEO çalışması gibi hissettirdiği için atlar. 2026'da bu iki nedenle hata:

  1. Geleneksel SEO: Google açıkça structured data'yı rich snippet'lar, yazar doğrulaması ve sıralama sinyalleri için kullanıyor
  2. GEO (Generative Engine Optimization): Gemini, Claude ve Perplexity gibi AI modelleri yazar güvenilirliğini ve gerçek atıfı doğrulamak için structured data çekiyor

Ne Uyguladım?

export function generateBlogPostSchema(post: PostData, url: string) {
  return {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "headline": post.title,
    "description": post.excerpt,
    "image": post.coverImage,
    "datePublished": post.date,
    "dateModified": post.date,
    "author": {
      "@type": "Person",
      "name": "Ömer Özbay",
      "url": "https://gucluyumhe.dev",
      "sameAs": [
        "https://github.com/gucluyumhe",
        "https://linkedin.com/in/omerozbay"
      ]
    },
    "publisher": {
      "@type": "Person",
      "name": "Ömer Özbay",
      "url": "https://gucluyumhe.dev"
    },
    "keywords": post.tags.join(", "),
    "inLanguage": post.locale === 'tr' ? "tr-TR" : "en-US",
    "url": url
  };
}

export default async function BlogPostPage({ params }: Props) {
  const post = await getPostBySlug(params.slug);
  const url = `https://gucluyumhe.dev/${params.locale}/blog/${params.slug}`;

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(generateBlogPostSchema(post, url))
        }}
      />
      <ArticleLayout post={post} />
    </>
  );
}

GitHub ve LinkedIn'e bağlanan sameAs dizisi GEO için kritik. AI modellerinin "Ömer Özbay"ı yalnızca bir ad dizisi değil, belirli bir varlık olarak ayrıştırmasına izin veriyor.


Karar 4: Edge-First Deployment (Vercel Edge Network)

Cazip Olan: Kontrol İçin VPS'de Self-Host

Vercel veya Netlify'ın kontrolden vazgeçmek olduğunu hisseden birçok geliştirici var. Bir portfolyo sitesi için bu yanlış bir değiş tokuş hesabı.

Bir Next.js uygulamasını self-host etmek şunları yönetmek anlamına gelir: SSL sertifikası yenileme, DDoS koruması, global CDN kurulumu, sıfır downtime deployment ve sağlık izleme.

Bir portfolyo için bu, sıfır değer üreten bakım yüküdür.

vercel.json'un Değeri

Tek bir konfigürasyon dosyası size global dağıtım, otomatik HTTPS ve güvenlik başlıkları dahil akıllı routing sağlar:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "X-XSS-Protection", "value": "1; mode=block" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
      ]
    },
    {
      "source": "/fonts/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    }
  ],
  "redirects": [
    { "source": "/blog/:slug", "destination": "/en/blog/:slug", "permanent": false }
  ]
}

Uzun vadeli kazanım: altyapıda sıfır bakım, otomatik ölçekleme ve yerleşik analitik.


Karar 5: Dinamik Site Haritası Oluşturma

Cazip Olan: Statik sitemap.xml

Statik bir sitemap.xml, yeni bir yazı yayınladığınızda manuel güncellemeler gerektirir. Üçüncü yazıda güncellemeyi unutursunuz. Onuncuda kalıcı olarak eski hale gelir.

Çözüm: Next.js App Router Sitemap

// app/sitemap.ts
import { MetadataRoute } from 'next';
import { getAllPosts } from '@/utils/markdown';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getAllPosts();
  const baseUrl = 'https://gucluyumhe.dev';

  const postEntries: MetadataRoute.Sitemap = posts.map((post) => ({
    url: `${baseUrl}/${post.locale}/blog/${post.slug}`,
    lastModified: new Date(post.date),
    changeFrequency: 'monthly',
    priority: 0.8,
  }));

  return [
    { url: baseUrl, lastModified: new Date(), changeFrequency: 'weekly', priority: 1 },
    { url: `${baseUrl}/en/blog`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.9 },
    { url: `${baseUrl}/tr/blog`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.9 },
    ...postEntries,
  ];
}

Yeni bir markdown dosyası ekleyin, site haritası bir sonraki build'de otomatik güncellenir. Sıfır bakım.


Karar 6: CSS'de Sorumlulukların Ayrılması

Çoğu Geliştiricinin Yaptığı

Her elementin üzerine satır içi Tailwind utility'leri, kod tabanında tutarsızlık yaratarak:

// Bakımı zor: stiller bileşenler arasında dağınık
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mt-8 mb-4 leading-tight tracking-tight">

Ne Yaptım: CSS Custom Properties ve Semantik Sınıflar

/* globals.css — tasarım token'ları custom property olarak */
:root {
  --color-text-primary: #0f172a;
  --color-text-secondary: #475569;
  --color-accent: #6366f1;
  --spacing-section: 4rem;
  --font-size-h1: clamp(2rem, 5vw, 3.5rem);
  --border-radius-card: 0.75rem;
}

[data-theme="dark"] {
  --color-text-primary: #f1f5f9;
  --color-text-secondary: #94a3b8;
  --color-accent: #818cf8;
}

/* Semantik bileşen sınıfları */
.article-title {
  font-size: var(--font-size-h1);
  font-weight: 700;
  color: var(--color-text-primary);
  line-height: 1.2;
  letter-spacing: -0.02em;
  margin-bottom: 1rem;
}

Uzun vadeli kazanım: tüm site genelinde vurgu rengini değiştirmek tek satır CSS değişikliği — 50 dosyada grep ve replace değil.


Acı Yaratan 2 Karar

Acı Veren Karar 1: Çok Erken Çok Fazla Sayfa Türü

Blog yazıları, proje vitrini, deneyim zaman çizelgesi, beceriler ızgarası, iletişim formu ve 404 için ayrı layout'lar oluşturdum. İlk versiyonda altı farklı layout türü.

Sonuç: her tasarım iterasyonu 6 dosyaya dokunmayı gerektirdi. Layout değişiklikleri bir angarya haline geldi.

Daha iyi yaklaşım: 2–3 genel layout shell ile başlayın. Uzmanlığı yalnızca içerik gerçekten gerektirdiğinde ekleyin.

Acı Veren Karar 2: Aynı Bileşende Dilleri Karıştırma

Başlangıçta bazı bileşenler her iki dilde hardcoded string içeriyordu:

// Bakım kabusu yarattı
<p>{locale === 'tr' ? 'Son yazılar' : 'Latest posts'}</p>

20'den fazla bileşende 15'ten fazla böyle string ile bu bir bakım kabusu haline geldi.

Daha iyi yaklaşım: i18n kütüphanesi olmadan bile, tüm görüntüleme string'lerini ilk günden itibaren locale-specific JSON dosyalarına çıkarın.


Mimarinin Özeti

gucluyumhe.dev Mimarisi (2026)
├── Icerik Katmani:     Markdown dosyaları, framework-agnostik
├── Routing Katmani:    Next.js App Router ([locale]/[slug])
├── Rendering Katmani:  React Server Components, statik HTML ciktisi
├── SEO Katmani:        JSON-LD semaları, dinamik site haritası, Metadata API
├── Stil Katmani:       CSS custom properties ve Tailwind utility'leri
├── Analitik Katmani:   Sunucu tarafli GA4, bloklama yapmayan
└── Altyapi Katmani:    Vercel Edge, sifir bakim

Üç yıl sonra React Server Components'in yerini daha iyi bir şey alırsa, yalnızca Rendering Katmanı değişir. İçerik, routing, SEO ve stil katmanları bağımsız. İşte uzun vadeli mimari kazanım bu.


Ders

Portfolyo siteleri mimari olarak başarısız olur çünkü geliştiriciler sürdürülebilirlik değil, lansmanın hızını optimize eder. İroni şu ki, upfront'ta yüzde 30 daha uzun süren kararlar — dinamik site haritaları, CSS custom properties, JSON-LD şemaları, locale konvansiyonları — sonraki yıllarda bakım sürtünmesinin yüzde 90'ını ortadan kaldırıyor.

40'tan fazla blog yazısı, 3 dil ve yeni bir özellik eklemek için 5 dakikası olan 2 yıl sonraki kendiniz için inşa edin. O versiyon, ilk günde aldığınız her ekstra karar için size teşekkür edecek.

Ö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.

Portfolyo Sitesinde Uzun Vadede Fark Yaratan Mimari Kararlar | Ömer Özbay Günlük | Ömer Özbay