İçeriğe geç
Tüm yazılar

Yazılım Mimarisi

Önce Statik: Next.js ile Hızlı, Ekonomik ve Sağlam Bir Site

Bu siteyi yazarken kendime tek bir kural koydum: sunucuya ihtiyacı olmayan hiçbir şeyi sunucuda üretmeyeceğim. Kâğıt üzerinde basitti; pratikte birkaç yerde ayağım takıldı. Gizlilik sayfasının neden 108 KB olduğunu da bu yazıda anlatıyorum.

Fahrettin Baştürk6 dk okuma

Bu siteyi yazarken kendime tek bir kural koydum: sunucuya hiç ihtiyacı olmayan bir şeyi sunucuda üretmeyeceğim. İçerik zaten kodun içinde duruyor; bir proje eklediğimde ya da yazı yazdığımda site baştan derleniyor. O hâlde her ziyaretçi için sayfayı yeniden oluşturmanın kimseye bir faydası yok. Sayfalar, paylaşım görselleri, RSS beslemesi; hepsi build sırasında dosya olarak üretiliyor ve CDN'den geliyor.

Kâğıt üzerinde basit. Pratikte birkaç yerde ayağım takıldı, onları not etmek istedim.

Bir çerez okumak her şeyi değiştirebiliyor

Next.js App Router'da sayfa ağacının herhangi bir yerinde cookies() ya da headers() çağırırsanız, o rota sessizce dinamik render'a geçiyor. Hata yok, uyarı yok; yalnızca build çıktısındaki küçük işaret değişiyor. İlk taslakta dil tercihini kök layout'ta çerezden okumayı düşünmüştüm. Yapsaydım bütün site her istekte yeniden render edilecekti ve muhtemelen bunu aylar sonra fark edecektim.

Çözüm, ziyaretçiye özel her kararı sayfanın dışına, middleware'e taşımak oldu. Dil tespiti ve yönlendirme orada yapılıyor; sayfa ise yalnızca URL'deki dili biliyor:

// Sayfa yalnızca URL'deki dili bilir; çerez okumaz, statik kalır.
export async function generateStaticParams() {
  return locales.map((lang) => ({ lang }));
}

export default async function Page(props: { params: Promise<{ lang: string }> }) {
  const lang = resolveLocale((await props.params).lang);
  const dict = await getDictionary(lang);
  // ...
}

Kimsenin görmediği yüz kilobayt

Asıl sürpriz burada çıktı. Gizlilik politikası, yani tek sayfalık bir metin, gzip'li hâlde 108 KB'tı. Kaynağı açtığımda 68 projenin uzun açıklamalarının da sayfanın içinde durduğunu gördüm.

Sebebi şu: Server Component'ten bir Client Component'e verdiğiniz her prop, tarayıcının o bileşeni canlandırabilmesi için HTML'in içine veri olarak gömülüyor. Menü her sayfada bulunan bir client bileşeniydi ve ben ona kolaylık olsun diye bütün sözlüğü vermiştim. Ekranda hiçbir şey değişmiyordu ama her sayfa, sitenin neredeyse bütün metnini yanında taşıyordu.

Client bileşenlere yalnızca kullandıkları alanları verdikten sonra gizlilik sayfası 12 KB'a, iletişim sayfası 110 KB'tan 15 KB'a, ana sayfa da 136 KB'tan 41 KB'a indi. Ders basit ama çok kolay unutuluyor: client bileşene nesnenin tamamını değil, gerçekten kullandığı kısmı ver.

Animasyon içeriği saklamamalı

Sık gördüğüm bir kalıp, içeriği JavaScript yüklenene kadar opacity: 0 ile gizleyip sonra yavaşça göstermek. Güzel görünüyor, ama JavaScript geç yüklenirse ya da hiç çalışmazsa sayfa boş kalıyor; arama motorları ve LCP de bundan hoşlanmıyor. Kaydırmaya bağlı efektleri artık saf CSS ile yapmak mümkün. Desteklemeyen tarayıcı içeriği animasyonsuz gösteriyor, o kadar:

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: reveal linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
    }
  }
}

Test yoksa build test olsun

Bu projede test altyapısı yok; açıkçası içerik ağırlıklı bir sitede yokluğunu çok hissetmedim. Onun yerine build'i bir kontrol noktası gibi kullanıyorum. İki dilde aynı projeler ve yazılar var mı, hizmet paketleri birebir eşleşiyor mu, Türkçe adres tablosunda çakışma var mı? Bir tutarsızlık varsa deploy duruyor ve hatayı canlıda değil, kendi terminalimde görüyorum.

Statik üretimi bir optimizasyon numarasından çok bir disiplin olarak görüyorum: kişiye özel olan her şey kenara, ortak olan her şey build'e. Geri kalanı, bu dengeyi farkında olmadan bozan küçük alışkanlıkları yakalamaktan ibaret.

Fahrettin Baştürk

Sistem Mimarı & Yazılım Danışmanı

Paylaş
  • Next.js
  • React
  • Performans
  • SEO