SSR çoğu projede otomatik olarak seçilmesi gereken bir yaklaşım değil. Doğru yerde kullanıldığında ilk içerik gösterimini, SEO’yu ve veri bağımlı sayfaları iyileştirebilir; yanlış yerde ise geliştirme ve operasyon maliyetini gereksiz yere artırır. Bu yüzden önce ne çözdüğünü, sonra ne bedel getirdiğini görmek gerekir.
Server-Side Rendering (SSR) Nedir?
SSR, web sayfalarının HTML’inin sunucu tarafında oluşturulup tarayıcıya gönderilmesi işlemidir. Geleneksel web sitelerinde zaten böyleydi, ama günümüz JavaScript framework’leri ile tekrar önem kazandı.
Basit SSR Örneği
// Express.js ile basit SSR
const express = require("express");
const React = require("react");
const ReactDOMServer = require("react-dom/server");
const App = require("./App");
const app = express();
app.get("*", (req, res) => {
// React component'ini server'da render et
const html = ReactDOMServer.renderToString(<App />);
// HTML template'i oluştur
const fullHtml = `
<!DOCTYPE html>
<html>
<head>
<title>SSR Example</title>
<meta charset="utf-8">
</head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`;
res.send(fullHtml);
});
app.listen(3000);
SSR vs CSR vs SSG Karşılaştırması
Client-Side Rendering (CSR)
// Geleneksel React SPA
function App() {
const [data, setData] = useState(null);
useEffect(() => {
// Client'ta data fetch edilir
fetch("/api/data")
.then(res => res.json())
.then(setData);
}, []);
if (!data) return <div>Loading...</div>;
return <div>{data.content}</div>;
}
Server-Side Rendering (SSR)
// Next.js getServerSideProps
export async function getServerSideProps(context) {
// Server'da data fetch edilir
const res = await fetch("http://localhost:3000/api/data");
const data = await res.json();
return {
props: {
data,
},
};
}
function Page({ data }) {
// Data zaten mevcut
return <div>{data.content}</div>;
}
Static Site Generation (SSG)
// Next.js getStaticProps
export async function getStaticProps() {
// Build time'da data fetch edilir
const res = await fetch("http://localhost:3000/api/data");
const data = await res.json();
return {
props: {
data,
},
revalidate: 60, // 60 saniyede bir revalidate
};
}
Next.js ile SSR Implementation
// pages/_app.js
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export default function MyApp({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
// Client-side navigation için hydration
console.log('App hydrated');
}, []);
return <Component {...pageProps} />;
}
// pages/product/[id].js
import { GetServerSideProps } from 'next';
interface Product {
id: string;
name: string;
description: string;
price: number;
}
interface ProductPageProps {
product: Product;
error?: string;
}
export default function ProductPage({ product, error }: ProductPageProps) {
if (error) {
return <div>Error: {error}</div>;
}
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<span>${product.price}</span>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org/",
"@type": "Product",
"name": product.name,
"description": product.description,
"offers": {
"@type": "Offer",
"price": product.price,
"priceCurrency": "USD"
}
})
}}
/>
</div>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const { id } = context.params!;
try {
// Her request'te data fetch et
const res = await fetch(`https://api.example.com/products/${id}`, {
headers: {
'User-Agent': 'NextJS SSR',
'Accept': 'application/json'
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const product = await res.json();
return {
props: {
product
}
};
} catch (error) {
console.error('SSR Error:', error);
return {
props: {
product: null,
error: 'Product not found'
}
};
}
};
SSR Performance Optimizations
1. Caching Strategies
// Redis ile server-side caching
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
export const getServerSideProps = async context => {
const { id } = context.params;
const cacheKey = `product:${id}`;
// Cache'den kontrol et
let product = await redis.get(cacheKey);
if (product) {
return {
props: {
product: JSON.parse(product),
cached: true,
},
};
}
// Cache miss - API'den fetch et
const res = await fetch(`/api/products/${id}`);
product = await res.json();
// Cache'e kaydet (5 dakika)
await redis.setex(cacheKey, 300, JSON.stringify(product));
return {
props: {
product,
cached: false,
},
};
};
2. Streaming SSR
// React 18 ile streaming SSR
import { renderToPipeableStream } from "react-dom/server";
import { Suspense } from "react";
function App() {
return (
<html>
<body>
<div id="root">
<Header />
<Suspense fallback={<div>Loading content...</div>}>
<SlowComponent />
</Suspense>
<Footer />
</div>
</body>
</html>
);
}
// Express route
app.get("/", (req, res) => {
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ["/client.js"],
onShellReady() {
// Shell ready - başlıkları gönder
res.statusCode = 200;
res.setHeader("Content-type", "text/html");
pipe(res);
},
onError(error) {
console.error("SSR Error:", error);
res.statusCode = 500;
res.send("Internal Server Error");
},
});
});
Hydration ve Client-Side Takeover
// Client-side hydration
import { hydrateRoot } from "react-dom/client";
import App from "./App";
// Server'dan gelen HTML'i "hydrate" et
const container = document.getElementById("root");
hydrateRoot(container, <App />);
// Hydration mismatch'leri handle et
function SafeHydrate({ children }) {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
return children;
}
// Kullanım
function MyComponent() {
return (
<div>
<h1>Always rendered</h1>
<SafeHydrate>
<ClientOnlyComponent />
</SafeHydrate>
</div>
);
}
SSR’nin Avantajları
- SEO Optimizasyonu: Search engine’ler HTML içeriği direkt okuyabilir
- İlk Sayfa Yüklenme Hızı: Content hemen görünür
- Social Media Sharing: Open Graph tag’leri çalışır
- Accessibility: JavaScript olmadan da içerik erişilebilir
SSR’nin Dezavantajları
- Sunucu Yükü: Her request için rendering gerekli
- Komplekslik: Client-server state sync zor
- Gecikmeli Interactivity: JavaScript yüklenmeden etkileşim yok
- Development Complexity: İki farklı environment
Sonuç
SSR, özellikle SEO ve performance’ın kritik olduğu projelerde çok değerli. Next.js, Nuxt.js gibi framework’ler sayesinde implementation artık çok daha kolay.
Hangi durumda hangi rendering strategy’yi kullanacağınız projenizin ihtiyaçlarına bağlı:
- SSR: SEO kritik, dynamic content
- SSG: Performans kritik, static content
- CSR: Interactivity kritik, dashboard tarzı uygulamalar
Günümüzde hibrit yaklaşımlar (SSR + CSR + SSG) en popüler trend!