Y J S

CSR vs SSR: ์–ธ์ œ, ์™œ ์„ ํƒํ• ๊นŒ?

๊ฐœ๋… ์š”์•ฝ

  • CSR (Client-Side Rendering): ๋ธŒ๋ผ์šฐ์ €๊ฐ€ JS๋กœ ์•ฑ์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋ Œ๋”๋ง.
  • SSR (Server-Side Rendering): ์„œ๋ฒ„๊ฐ€ HTML์„ ๋งŒ๋“ค์–ด ์ „๋‹ฌํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•˜์ด๋“œ๋ ˆ์ด์…˜.

๋™์ž‘ ํ๋ฆ„ ๊ฐ„๋‹จ ๋น„๊ต

  • CSR: HTML ๊ณจ๊ฒฉ โ†’ JS ๋ฒˆ๋“ค ๋กœ๋“œ โ†’ ๋ฐ์ดํ„ฐ ํŽ˜์นญ โ†’ ํด๋ผ์ด์–ธํŠธ ๋ Œ๋”.
  • SSR: ์š”์ฒญ ์‹œ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ํŽ˜์นญ/๋ Œ๋” โ†’ ์™„์„ฑ๋œ HTML ์‘๋‹ต โ†’ ํ•˜์ด๋“œ๋ ˆ์ด์…˜.

์žฅ๋‹จ์  ์ •๋ฆฌ

  • CSR ์žฅ์ 
    • ๋ผ์šฐํŒ… ์ „ํ™˜์ด ๋น ๋ฅด๊ณ  ํ’๋ถ€ํ•œ ์ธํ„ฐ๋ž™์…˜์— ์œ ๋ฆฌ.
    • CDN ๋ฐฐํฌ๊ฐ€ ์‰ฝ๊ณ  ์„œ๋ฒ„ ๋ถ€๋‹ด์ด ์ ์Œ.
    • ๋ณต์žกํ•œ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ๊ถํ•ฉ์ด ์ข‹์Œ.
  • CSR ๋‹จ์ 
    • ์ดˆ๊ธฐ ๋กœ๋“œ๊ฐ€ ๋А๋ฆด ์ˆ˜ ์žˆ๊ณ  JS ์˜์กด๋„๊ฐ€ ํผ.
    • ๊ธฐ๋ณธ SEO์— ๋ถˆ๋ฆฌ(์‚ฌ์ „ ๋ Œ๋”๊ฐ€ ์—†์Œ).
  • SSR ์žฅ์ 
    • ์ดˆ๊ธฐ ํ‘œ์‹œ(First Paint/TTFB ์ดํ›„)๊ฐ€ ๋น ๋ฅด๊ณ  SEO์— ์œ ๋ฆฌ.
    • ์ €์‚ฌ์–‘/์ €์„ฑ๋Šฅ ๊ธฐ๊ธฐ์—์„œ๋„ ์ตœ์ดˆ ์ฒด๊ฐ์ด ์ข‹์Œ.
  • SSR ๋‹จ์ 
    • ์„œ๋ฒ„ ๋ถ€ํ•˜ ์ฆ๊ฐ€, ์บ์‹ฑ/์Šค์ผ€์ผ ์ „๋žต ํ•„์š”.
    • ์š”์ฒญ๋งˆ๋‹ค ๋ Œ๋” โ†’ TTFB ์ฆ๊ฐ€ ๊ฐ€๋Šฅ.

Next.js ๊ด€์ ์—์„œ์˜ ์„ ํƒ

  • SSG/ISR: ๋นŒ๋“œ ํƒ€์ž„(SSG) ํ˜น์€ ์ฃผ๊ธฐ์  ์žฌ์ƒ์„ฑ(ISR)์œผ๋กœ ์„ฑ๋Šฅ๊ณผ SEO๋ฅผ ๋ชจ๋‘ ํ™•๋ณด.
  • App Router(Server/Client Components)
    • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ธฐ๋ณธ(SSR๋กœ ๋ฐ์ดํ„ฐ ๊ฐ€๊นŒ์ด). ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ/์ด๋ฒคํŠธ๊ฐ€ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ use client๋กœ ๋ถ„๋ฆฌ.
  • ๋ฐ์ดํ„ฐ ํŒจ์นญ
    • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ: fetch() ๊ธฐ๋ณธ ์บ์‹œ/์žฌ๊ฒ€์ฆ ์˜ต์…˜์œผ๋กœ ISR ์œ ์‚ฌ ํšจ๊ณผ.
    • ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ: useEffect/SWR/React Query ๋“ฑ์œผ๋กœ CSR ํŒจํ„ด.

์ฝ”๋“œ ์˜ˆ์‹œ

  • SSR(์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ, Next.js App Router)
// app/articles/page.tsx (Server Component)
export default async function Page() {
  const res = await fetch("https://api.example.com/articles", {
    cache: "no-store",
  });
  const articles = await res.json();
  return (
    <main>
      <h1>Articles</h1>
      <ul>
        {articles.map((a: any) => (
          <li key={a.id}>{a.title}</li>
        ))}
      </ul>
    </main>
  );
}
  • CSR(ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ)
"use client";
import { useEffect, useState } from "react";

export default function Page() {
  const [data, setData] = useState<any[] | null>(null);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    fetch("/api/items")
      .then((r) => r.json())
      .then((d) => setData(d))
      .finally(() => setLoading(false));
  }, []);
  if (loading) return <div>Loading...</div>;
  return (
    <ul>
      {data?.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

์–ด๋–ค ๊ฑธ ์„ ํƒํ• ๊นŒ? (์ฒดํฌ๋ฆฌ์ŠคํŠธ)

  • SEO๊ฐ€ ์ค‘์š”ํ•œ๊ฐ€? โ†’ SSR/SSG/ISR ์šฐ์„  ๊ณ ๋ ค.
  • ์‹ค์‹œ๊ฐ„ ๊ฐœ์ธํ™”, ์‚ฌ์šฉ์ž๋ณ„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ๋‚˜? โ†’ SSR+ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ํ˜ผํ•ฉ ๋˜๋Š” ์ˆœ์ˆ˜ CSR.
  • ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ๊ฐ„์ด ์ค‘์š”ํ•œ ๋žœ๋”ฉ/๋งˆ์ผ€ํŒ… ํŽ˜์ด์ง€์ธ๊ฐ€? โ†’ SSG/ISR.
  • ๋ณต์žกํ•œ ์ธํ„ฐ๋ž™์…˜/์˜คํ”„๋ผ์ธ ๊ธฐ๋Šฅ ์ค‘์‹ฌ์ธ๊ฐ€? โ†’ CSR ๋˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ.

๊ฒฐ๋ก 

  • ๋‹จ์ผ ํ•ด๋ฒ•์€ ์—†๋‹ค. ๊ณต๊ฐœ ํŽ˜์ด์ง€๋Š” SSR/SSG๋กœ, ์•ฑ ๋‚ด๋ถ€๋Š” CSR๋กœ ๊ตฌ์„ฑํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ๊ฐ€ ํ˜„์‹ค์ ์ธ ์ตœ์ ํ•ด๋‹ค.
  • Next.js App Router์—์„œ๋Š” ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ž์–ด ์‚ฌ์šฉํ•˜๋ฉฐ, ์บ์‹œ/์žฌ๊ฒ€์ฆ ์ „๋žต์œผ๋กœ ์„ฑ๋ŠฅยทSEOยท๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ํ•จ๊ป˜ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.