Saf HTML ve Tailwind Bazen En İyi Seçim — Dürüst Bir Mimari Analiz
Kıdemli mühendisler arasında büyüyen bir karşı hareket var: içerik ağırlıklı web sitelerinde React, Next.js, Nuxt ve Gatsby'den uzaklaşarak temellere dönmek — saf HTML, CSS ve minimal JavaScript.
Her iki paradigmada da production sistemler inşa etmiş bir Senior Full Stack Architect olarak, bu kararın ne zaman doğru olduğunu, ne zaman hata olduğunu ve gerçek ödünleşimlerin nasıl göründüğünü dürüst ve veriye dayalı bir şekilde analiz edeceğim.
Bu bir "framework'ler kötü" argümanı değil. Bu bir mimari karar çerçevesidir.
Bağlam: Bu Kararı Tetikleyen Neydi?
Bir blog tipi içerik sitesi tasarladığımda ilk içgüdüm Next.js'e uzanmaktı. Onu iyi tanıyordum, production'da güveniyordum ve mükemmel SEO araçları sunuyordu. Ama spesifikasyonu yazarken kendime sürekli aynı soruyu soruyordum:
Bu framework şu an gerçekte hangi sorumu çözüyor?
Statik markdown içerik, gerçek zamanlı veri yok, karmaşık state yönetimi yok ve yüksek SEO gereksinimleri olan bir blog için dürüst cevap şuydu: framework sahip olmadığım sorunları çözüyordu.
JavaScript Framework'ün Statik Site İçin Gerçek Maliyeti
Varsayılan ayarlarla bir Next.js blog sitesi deploy ettiğinizde, her ziyaretçiye şunları gönderirsiniz:
| Varlık | Yaklaşık Boyut | |:---|:---| | React runtime | 42kb gzip | | Next.js client runtime | 95kb gzip | | React DOM reconciler | 35kb gzip | | Gerçek sayfa içeriğiniz | 5–15kb | | Toplam ek yük | İçerikten önce 172–187kb |
Yalnızca metin ve kod bloklarından oluşan bir blog yazısı sayfası için, gerçek içeriği okumak için gereken JavaScript'in neredeyse 15 katını gönderiyorsunuz.
Saf HTML ve temizlenmiş Tailwind ile:
| Varlık | Yaklaşık Boyut | |:---|:---| | CSS (temizlenmiş Tailwind) | 8–15kb gzip | | Alpine.js (karanlık mod için) | 7kb gzip | | Gerçek sayfa içeriğiniz | 5–15kb | | Toplam ek yük | 20–37kb |
Bu yaklaşık 5 ila 8 kat daha az yük demek — doğrudan daha hızlı LCP ve daha düşük bant genişliği maliyetlerine dönüşüyor.
Saf HTML ve Tailwind'in Kazandığı Durumlar
1. İçerik Haftada Birden Az Değişiyor
Siteniz öncelikle statik içerik sunuyorsa — makaleler, portfolyolar, belgeler — bir framework'ün yeniden render döngüsü, hidrasyonu ve JavaScript bundle'ı kullanıcı deneyimine sıfır değer katıyor.
CDN'den sunulan statik bir HTML dosyasının küresel TTFB'si 20ms'nin altındadır. ISR ile bile Next.js sayfaları genellikle runtime ek yükü nedeniyle 30–100ms daha uzun sürer.
2. Bileşenler Arası Paylaşılan State Bağımlılığı Yok
Karanlık mod geçişi? 8 satırlık Alpine.js içeren bir script etiketi. Mobil navigasyon? Başka 10 satır. Kod bloklarında panoya kopyalama? 5 satır.
Üç element arasında karanlık mod geçişini koordine etmek için React'ın bileşen ağacına ihtiyacınız yok.
<!-- Saf HTML'de karanlık mod — Alpine.js, 8 satır -->
<html x-data="{ dark: false }" :class="{ 'dark': dark }">
<body>
<button @click="dark = !dark" aria-label="Karanlık modu değiştir">
<span x-show="!dark">Karanlık</span>
<span x-show="dark">Aydınlık</span>
</button>
</body>
</html>
3. Build Pipeline'ı Tamamen Kontrol Ediyorsunuz
Özel bir statik site oluşturucuyla, hangi fontların nasıl yükleneceği, hangi script'lerin ne zaman çalışacağı ve tam olarak hangi HTML'in gönderileceği üzerinde tam kontrol sahibisiniz.
// Yaklaşık 80 satırda tam bir statik site oluşturucu
import fs from 'fs/promises';
import path from 'path';
import matter from 'gray-matter';
import { marked } from 'marked';
async function buildPost(filename: string): Promise<void> {
const raw = await fs.readFile(path.join('./content/posts', filename), 'utf-8');
const { data: frontmatter, content } = matter(raw);
const html = await marked(content);
const page = `<!DOCTYPE html>
<html lang="${frontmatter.locale || 'tr'}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${frontmatter.title}</title>
<meta name="description" content="${frontmatter.excerpt}">
<link rel="stylesheet" href="/styles.css">
</head>
<body class="max-w-3xl mx-auto px-4 py-12">
<article>${html}</article>
</body>
</html>`;
const slug = filename.replace('.md', '');
await fs.mkdir(path.join('./dist/blog', slug), { recursive: true });
await fs.writeFile(path.join('./dist/blog', slug, 'index.html'), page);
}
Framework'ün Hâlâ Kazandığı Durumlar
Bu bir anti-framework argümanı değil. Next.js veya benzer bir framework'ün doğru seçim olduğu açık senaryolar var:
| Senaryo | Saf HTML | Next.js | |:---|:---|:---| | Statik blog, kimlik doğrulama yok | Önerilen | Çalışır | | Kimlik doğrulama gerektiren dashboard | Önerilmez | Önerilen | | Günde birden fazla içerik güncellemesi | Manuel rebuild gerekir | ISR ile önerilen | | Karmaşık etkileşimli bileşenler | Pratik değil | Önerilen | | Sepet state'li e-ticaret | Pratik değil | Önerilen | | Geliştirici belgeleri sitesi | Önerilen | Çalışır | | Gerçek zamanlı analizli portfolyo | Ekstra iş gerektirir | Önerilen |
Temel soru her zaman şudur: projemde bileşenler arasında paylaşılması gereken etkileşimli state var mı?
Evet ise framework kullanın. Hayır ise saf HTML'i ciddiye alın.
Hibrit Yaklaşım: Gerçekte Ne Kullandığım
Kapsamlı testlerden sonra, içerik odaklı siteler için mimarim bir hibrite dönüştü:
- Ana kabuk, routing ve SEO metadata API için Next.js App Router
- Tüm statik içerik için React Server Components — blog yazıları, proje sayfaları, sıfır client JavaScript
- Yalnızca gerçek etkileşim için minimal client components (navigasyon geçişi, yer imi özelliği)
- Utility styling için Tailwind CSS
- State yönetim kütüphanesi yok — Redux yok, Zustand yok
Bu, bana Vercel'in deployment avantajlarını, Next.js Metadata API'ının SEO araçlarını ve esasen statik HTML'nin performansını sağlıyor — çünkü React Server Components statik HTML'e derleniyor.
// Bu server component tarayıcıya SIFIR JavaScript gönderiyor
// Sunucuda saf HTML olarak render ediliyor
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
return (
<article className="max-w-3xl mx-auto px-4 py-12">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
</article>
);
// Sonuç: saf HTML, JavaScript hidrasyonu yok, PageSpeed 100/100
}
Mimari Karar Çerçevesi
Herhangi bir yeni projeyi değerlendirirken bu 5 soruyu sırayla sorun:
1. İçerik günde birden fazla değişecek mi?
Evet — ISR veya SSR düşünün (framework kazanır)
Hayır — statik oluşturma çalışır
2. Herhangi bir UI elementi paylaşılan reactive state gerektiriyor mu?
Evet — framework bileşen modeli değerlidir
Hayır — Alpine.js veya vanilla JavaScript yeterlidir
3. Kimlik doğrulama ve kullanıcı oturumlarına ihtiyacınız var mı?
Evet — auth kütüphanesiyle framework kullanın
Hayır — statik HTML mükemmel çalışır
4. Takım 3 mühendisten fazla büyüyecek mi?
Evet — framework gelenekleri oryantasyon sürtünmesini azaltır
Hayır — saf HTML bakımı daha kolaydır
5. Bir CMS veya API ile sıkı entegrasyona ihtiyacınız var mı?
Evet — framework tercih edin (Next.js API route'ları, veri çekme)
Hayır — build-time markdown işleme daha basittir
Sonuç: Araçlar Kimlik Değildir
En kötü mühendislik kararı, bildiğiniz için ya da sektörün varsayılanı olduğu için bir araç seçmektir. En iyi mühendislik kararı, gerçek probleminizi en az karmaşıklıkla çözen aracı seçmektir.
2026'da bir kişisel blog için saf HTML ve Tailwind bir adım geri değil — her önemli metrikte çoğu framework tabanlı blogu geride bırakan kasıtlı, yüksek performanslı, düşük bakımlı bir mimari olabilir.
Karmaşık bir SaaS dashboard'u, kimlik doğrulama yoğun bir platform veya gerçek zamanlı uygulama için ise Next.js veya benzer bir framework kesinlikle doğru seçimdir.
Probleminizi bilin. Aracı seçin. Sonra iyi uygulayın.
