Skip to content

Server-Side Rendering (SSR): Ne Zaman Gerekli, Ne Zaman Gereksiz?

Published: at 09:26 PMSuggest an edit

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ı

  1. SEO Optimizasyonu: Search engine’ler HTML içeriği direkt okuyabilir
  2. İlk Sayfa Yüklenme Hızı: Content hemen görünür
  3. Social Media Sharing: Open Graph tag’leri çalışır
  4. Accessibility: JavaScript olmadan da içerik erişilebilir

SSR’nin Dezavantajları

  1. Sunucu Yükü: Her request için rendering gerekli
  2. Komplekslik: Client-server state sync zor
  3. Gecikmeli Interactivity: JavaScript yüklenmeden etkileşim yok
  4. 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ı:

Günümüzde hibrit yaklaşımlar (SSR + CSR + SSG) en popüler trend!

Kaynaklar



Previous Post
GANs: Generator ve Discriminator ile Veri Üretimi
Next Post
PHP exec(): Process Yönetimi ve Güvenlik