Skip to content

Ders 07: SSR ve Rendering Modes

  • SSR (Server-Side Rendering) nedir ve neden kullanmalıyız?
  • Selective SSR ile route bazlı kontrol
  • SPA mode tam olarak nedir?
  • Static prerendering nasıl yapılır?
  • Streaming SSR ile performans optimizasyonu
  • Her rendering modu ne zaman kullanılmalı?

TanStack Start, benzersiz bir şekilde birden fazla rendering modunu aynı uygulamada destekler. Bu sayede her route için en uygun rendering stratejisini seçebilirsiniz.

Rendering ModuAçıklamaSEOİlk YüklemeKullanım Durumu
SSRSunucuda HTML oluşturulur✅ Mükemmel🟡 OrtaDinamik içerik, SEO kritik
SPA ModeTamamen client-side rendering❌ Yok🟡 OrtaAdmin panel, dashboard
Static PrerenderingBuild zamanında HTML oluşturulur✅ Mükemmel✅ HızlıStatik içerik, blog
Selective SSRRoute bazlı seçim✅ Özelleştirilebilir✅ ÖzelleştirilebilirHibrit uygulamalar

SSR, sayfanın HTML’inin sunucuda oluşturulup tarayıcıya gönderilmesi işlemidir.

1. Kullanıcı URL'e gider
2. Sunucu route'un loader'ını çalıştırır
3. Sunucu HTML'i oluşturur (verilerle birlikte)
4. HTML tarayıcıya gönderilir
5. Tarayıcı HTML'i gösterir (JS yüklenmeden)
6. JS yüklendikten sonra sayfa interaktif olur

TanStack Start’ta tüm route’lar varsayılan olarak SSR modundadır:

src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
// Varsayılan olarak SSR aktiftir!
component: HomePage,
loader: async () => {
// Bu loader sunucuda çalışır
const data = await fetch('https://api.example.com/data').then(r => r.json())
return { data }
},
})
function HomePage() {
const { data } = Route.useLoaderData()
return (
<div>
<h1>Ana Sayfa</h1>
<p>{data.message}</p>
</div>
)
}
// SEO için mükemmel
// src/routes/blog/$slug.tsx
export const Route = createFileRoute('/blog/$slug')({
loader: async ({ params }) => {
// Arama motorları içeriği görebilir
const post = await getBlogPost(params.slug)
return post
},
})
// İlk yükleme hızlı (içerik hazır gelir)
// Sosyal medya paylaşımları için preview görüntüleri
// Dezavantajlar:
// - Sunucu yükü artar (her istekte HTML oluşturulur)
// - TTFB (Time to First Byte) artabilir
// - Karmaşık server-side logic gerektirebilir

TanStack Start’ın en güçlü özelliği, her route için farklı rendering modu seçebilmenizdir.

import { createFileRoute } from '@tanstack/react-router'
// ✅ SSR aktif (varsayılan)
export const Route = createFileRoute('/blog/$slug')({
component: BlogPost,
})
// ❌ SSR kapalı (SPA mode)
export const Route = createFileRoute('/admin/dashboard')({
component: AdminDashboard,
// SSR'ı devre dışı bırak
ssr: false,
})
// src/routes/index.tsx - Ana sayfa (SSR aktif)
export const Route = createFileRoute('/')({
component: HomePage,
// ssr: true (varsayılan)
})
// src/routes/blog/index.tsx - Blog listesi (SSR aktif)
export const Route = createFileRoute('/blog')({
component: BlogList,
loader: async () => {
// SEO için önemli
return { posts: await getAllPosts() }
},
})
// src/routes/admin/index.tsx - Admin panel (SSR kapalı)
export const Route = createFileRoute('/admin')({
component: AdminPanel,
ssr: false, // SPA mode
loader: async () => {
// Sadece client'te çalışır
return { stats: await getAdminStats() }
},
})
// src/routes/dashboard.tsx - Layout (SSR kapalı)
export const Route = createFileRoute('/dashboard')({
component: DashboardLayout,
ssr: false, // Tüm dashboard SPA mode'da
})
// src/routes/dashboard/index.tsx - Ana sayfa (Layout'tan miras alır)
export const Route = createFileRoute('/dashboard/')({
component: DashboardHome,
// SSR kapalı (parent'tan miras)
})
// src/routes/dashboard/settings.tsx - Ayarlar (Layout'tan miras alır)
export const Route = createFileRoute('/dashboard/settings')({
component: Settings,
// SSR kapalı (parent'tan miras)
})

SPA mode’da sayfa tamamen client-side render edilir. HTML ilk başta boş gelir, JS yüklendikten sonra içerik oluşturulur.

Kullanım Durumları:

  • Admin panel ve dashboard’lar
  • Giriş yapmış kullanıcı içi sayfalar
  • SEO gerektirmeyen uygulamalar
  • Gerçek zamanlı veri gösteren sayfalar

Kullanılmamalı:

  • Herkese açık içerik sayfaları
  • Blog ve haber siteleri
  • E-commerce ürün sayfaları
src/routes/admin/dashboard.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/admin/dashboard')({
component: AdminDashboard,
ssr: false, // SPA mode aktif
loader: async () => {
// Bu loader SADECE client'te çalışır
const stats = await fetch('/api/admin/stats').then(r => r.json())
return { stats }
},
})
function AdminDashboard() {
const { stats } = Route.useLoaderData()
return (
<div>
<h1>Admin Dashboard</h1>
<p>Toplam Kullanıcı: {stats.totalUsers}</p>
<p>Bugünkü Görüntüleme: {stats.views}</p>
</div>
)
}
// Avantajlar:
// 1. Sunucu yükü azalır (HTML oluşturma yok)
// 2. TTFH (Time to First Byte) düşük
// 3. Karmaşık client-side logic daha kolay
// 4. Gerçek zamanlı güncellemeler için uygun
export const Route = createFileRoute('/realtime/chat')({
component: ChatRoom,
ssr: false, // WebSocket bağlantısı için ideal
})

Static prerendering, sayfaların build zamanında HTML olarak oluşturulmasıdır. Her istekte değil, build sırasında bir kez oluşturulur.

Build zamanı:
npm run build
1. Tüm prerender route'ları toplanır
2. Her route için loader çalıştırılır
3. HTML dosyaları oluşturulur
4. Dosyalar disk'e kaydedilir
Çalışma zamanı:
Kullanıcı /hakkimizda'e gider
Statik HTML dosyası direkt sunulur
src/routes/hakkimizda.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/hakkimizda')({
component: AboutPage,
// Bu sayfa build zamanında prerender edilir
loader: async () => {
// Build zamanında bir kez çalışır
return {
title: 'Hakkımızda',
content: 'Biz harika bir şirketiz...',
}
},
})
function AboutPage() {
const { title, content } = Route.useLoaderData()
return (
<div>
<h1>{title}</h1>
<p>{content}</p>
</div>
)
}
// ✅ Prerender (Build zamanında)
export const Route = createFileRoute('/about')({
component: AboutPage,
loader: async () => {
// npm run build sırasında bir kez çalışır
return { content: 'Statik içerik' }
},
// Prerender varsayılan olarak statik sayfalar için kullanılır
})
// ✅ SSR (Her istekte)
export const Route = createFileRoute('/blog/$slug')({
component: BlogPost,
loader: async ({ params }) => {
// Her istekte çalışır
return await getBlogPost(params.slug)
},
// Dinamik içerik için ideal
})

Streaming SSR, HTML’in parça parça gönderilmesi tekniğidir. Bu sayede sayfa daha hızlı görünmeye başlar.

Normal SSR:
[Layout hazırlanır] → [Veri çekilir] → [İçerik hazırlanır] → [Tüm HTML gönderilir]
└───────────────────── 5 saniye ─────────────────────┘
Streaming SSR:
[Layout gönderilir] → [Yükleniyor gösterilir] → [Veri çekilir] → [İçerik gönderilir]
└─ 100ms ─┘ └───── 2 saniye ──────┘
Kullanıcı deneyimi: 100ms'te bir şey görür, 2 saniyede içerik gelir
// src/routes/urunler/$id.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/urunler/$id')({
component: UrunDetayPage,
loader: async ({ params }) => {
// Layout hemen gönderilir
return {
// Ana veri
urun: await getUrun(params.id),
// Yorumlar daha sonra gelir (streaming)
yorumlar: await getYorumlar(params.id),
}
},
})
function UrunDetayPage() {
const { urun, yorumlar } = Route.useLoaderData()
return (
<div>
{/* Hemen görünür */}
<h1>{urun.baslik}</h1>
<p>{urun.aciklama}</p>
{/* Yorumlar yüklendiğinde görünür */}
<YorumlarListesi yorumlar={yorumlar} />
</div>
)
}
import { Suspense } from 'react'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
component: DashboardPage,
loader: async () => {
return {
// Hızlı yüklenen veri
user: await getCurrentUser(),
}
},
})
function DashboardPage() {
const { user } = Route.useLoaderData()
return (
<div>
<h1>Hoş geldin, {user.ad}!</h1>
{/* Yavaş yüklenen bileşenler için Suspense */}
<Suspense fallback={<div>Yükleniyor...</div>}>
<YavasBilesen />
</Suspense>
</div>
)
}
// Bu bileşen streaming ile yüklenir
function YavasBilesen() {
const { data } = useQuery({
queryKey: ['yavas-veri'],
queryFn: () => fetch('/api/yavas').then(r => r.json()),
})
return <div>{data.content}</div>
}

Route'u belirle
SEO gerekiyor mu?
├─ Evet → SSR veya Static
│ ↓
│ İçerik dinamik mi?
│ ├─ Evet → SSR
│ └─ Hayır → Static Prerender
└─ Hayır → SPA Mode
Giriş gerekli mi?
├─ Evet → SPA Mode (mükemmel)
└─ Hayır → Yine de SPA (admin için)
// ✅ 1. Blog Detay - SSR (SEO + Dinamik)
export const Route = createFileRoute('/blog/$slug')({
component: BlogPost,
// ssr: true (varsayılan)
loader: async ({ params }) => await getPost(params.slug),
})
// ✅ 2. Hakkımızda - Static (SEO + Statik)
export const Route = createFileRoute('/hakkimizda')({
component: AboutPage,
// Prerender edilir
})
// ✅ 3. Admin Dashboard - SPA (SEO yok + Dinamik)
export const Route = createFileRoute('/admin/dashboard')({
component: AdminDashboard,
ssr: false,
})
// ✅ 4. Kullanıcı Profil - SPA (SEO yok + Giriş gerekli)
export const Route = createFileRoute('/profil/$userId')({
component: UserProfile,
ssr: false,
})
// ✅ 5. Ana Sayfa - SSR (SEO + Dinamik)
export const Route = createFileRoute('/')({
component: HomePage,
// ssr: true (varsayılan)
loader: async () => await getFeaturedPosts(),
})

Hadi bir e-commerce sitesi için farklı rendering modlarını kullanalım!

// src/routes/index.tsx - Ana sayfa (SSR)
export const Route = createFileRoute('/')({
component: HomePage,
// SEO için SSR
loader: async () => {
const [featured, categories, banners] = await Promise.all([
getFeaturedProducts(),
getCategories(),
getBanners(),
])
return { featured, categories, banners }
},
})
function HomePage() {
const { featured, categories, banners } = Route.useLoaderData()
return (
<div>
<BannerSlider banners={banners} />
<CategoryList categories={categories} />
<ProductGrid products={featured} />
</div>
)
}
// src/routes/urunler/$slug.tsx - Ürün detay (SSR)
export const Route = createFileRoute('/urunler/$slug')({
component: UrunDetayPage,
// SEO için kritik!
loader: async ({ params }) => {
const urun = await getUrun(params.slug)
if (!urun) {
throw notFound()
}
return { urun }
},
})
function UrunDetayPage() {
const { urun } = Route.useLoaderData()
return (
<div>
<h1>{urun.baslik}</h1>
<p>{urun.aciklama}</p>
<p>Fiyat: ₺{urun.fiyat}</p>
</div>
)
}
// src/routes/sepet.tsx - Sepet (SPA)
export const Route = createFileRoute('/sepet')({
component: SepetPage,
ssr: false, // SEO gerekmez
loader: async () => {
// Kullanıcıya özel veri
return { sepet: await getSepet() }
},
})
function SepetPage() {
const { sepet } = Route.useLoaderData()
return (
<div>
<h1>Alışveriş Sepetim</h1>
<SepetListesi urunler={sepet.urunler} />
</div>
)
}
// src/routes/hesap/index.tsx - Hesap (SPA + Auth)
export const Route = createFileRoute('/hesap')({
component: HesapPage,
ssr: false, // Giriş gerekli
loader: async () => {
const session = await getSession()
if (!session.data.userId) {
throw redirect({ to: '/giris' })
}
return {
siparisler: await getSiparisler(session.data.userId),
adresler: await getAdresler(session.data.userId),
}
},
})
function HesapPage() {
const { siparisler, adresler } = Route.useLoaderData()
return (
<div>
<h1>Hesabım</h1>
<SiparisListesi siparisler={siparisler} />
<AdresListesi adresler={adresler} />
</div>
)
}
// src/routes/ss/index.tsx - SSS (Static)
export const Route = createFileRoute('/sss')({
component: SSSPage,
// Statik içerik, prerender edilebilir
loader: async () => {
return {
sorular: [
{ q: 'Kargo ne kadar?', a: '100 TL üzeri ücretsiz' },
{ q: 'İade nasıl yapılır?', a: '30 gün içinde iade' },
],
}
},
})
function SSSPage() {
const { sorular } = Route.useLoaderData()
return (
<div>
<h1>Sıkça Sorulan Sorular</h1>
{sorular.map((s) => (
<div key={s.q}>
<h3>{s.q}</h3>
<p>{s.a}</p>
</div>
))}
</div>
)
}

Bu derste öğrendiklerimiz:

KonuAçıklama
SSRSunucuda HTML oluşturma (varsayılan)
SPA ModeTamamen client-side rendering (ssr: false)
Static PrerenderingBuild zamanında HTML oluşturma
Selective SSRRoute bazlı rendering modu seçimi
Streaming SSRParça parça HTML gönderme
SuspenseYavaş yüklenen bileşenler için loading
SenaryoÖnerilen Mod
Blog yazısıSSR
Ana sayfaSSR
Admin panelSPA
Kullanıcı profiliSPA
Hakkımızda sayfasıStatic
SSS sayfasıStatic
SepetSPA
Ürün detaySSR

Blog uygulamanız için her sayfaya uygun rendering modunu seçin:

// Blog listesi -> ?
// Blog detay -> ?
// Yorum yaz (giriş gerekli) -> ?
// Yazar profili -> ?

Admin panelinizi SPA mode’a çevirin:

// Tüm admin route'ları için ssr: false yapın
// Nested routes için inheritance'ı test edin

Şu yapının her route’u için doğru modu seçin:

/ (ana sayfa)
├── /blog (blog listesi)
│ └── /blog/$slug (detay)
├── /urunler (ürün listesi)
│ └── /urunler/$id (ürün detay)
├── /sepet (sepet)
└── /admin (admin panel)
├── /admin/dashboard
└── /admin/urunler

🚀 Sonraki Ders: Authentication ve Authorization

Section titled “🚀 Sonraki Ders: Authentication ve Authorization”

Bir sonraki derste şunları öğreneceksiniz:

  • 🔐 Authentication vs Authorization farkı
  • 🍪 Session tabanlı auth
  • 🔑 JWT token yönetimi
  • 🛡️ Route koruma (protected routes)
  • 📋 Role-based access control
  • 🎯 OAuth entegrasyonu

  1. SSR her zaman daha mı iyidir?
  2. SPA mode SEO için hiç uygun değil mi?
  3. Static prerendering ne zaman yeniden yapılır?

Bir sonraki derste görüşmek üzere! 👋