SSR 과 CSR 이해하기 -(2) SSR, CSR 코드 살펴보고 비교하기

가연·2024년 10월 31일
3

우테코

목록 보기
8/10
post-thumbnail

SSR은 서버에서 html을 생성하고, CSR은 브라우저에서 html 을 생성한다.

그렇다면 예시를 보며 비교해보자
우리는 api 서버에서 영화 목록을 불러오고, 불러온 영화목록을 화면에 보여줘야하는 상황이다. 이때 뼈대 html 코드는 다음과 같다.

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="../assets/styles/reset.css" />
...생략
    <script src="./"></script>
    <title>영화 리뷰</title>
  </head>
  <body>
    <div id="wrap">
      <main>
          <section>
            <h2>지금 인기 있는 영화</h2>
            <ul class="thumbnail-list">
              <!--${MOVIE_ITEMS_PLACEHOLDER}-->
            </ul>
          </section>
        </main>
 ...생략

다음과 같은 html이 있을때 데이터를 fetch해서 최종 html 을 어떻게 완성하는지
SSR, SSR+CSR 별로 살펴보자.

순수한 SSR

🙎 가정 : 초기 페이지에 접속하면 영화 목록을 확인하고 싶어요
이때, 초기 렌더링 속도를 줄이기 위해 SSR 를 활용하고 싶어요.

참고 : 다음 코드는 초기 ssr인 jsp 같이 api 서버도 같이 처리하는 서버가 아닌 js로 구현한 SSR이다.

⭐️ 렌더링을 위한 nodejs 코드

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const router = Router();

const renderMoviePage = async (req, res) => {
  try {
    const templatePath = path.join(__dirname, "../../views", "index.html");

    const movies = await fetchMovies(); 
   // 1️⃣ 영화 목록 fetch
    const featuredMovie = movies[0];
    const moviesHTML = renderMovieItems(movies);
    let template = fs.readFileSync(templatePath, "utf-8");
    const renderedHTML = template
      .replace("<!--${MOVIE_ITEMS_PLACEHOLDER}-->", moviesHTML);
	// 2️⃣ html 생성
    res.send(renderedHTML);
  } catch (error) {
    console.error(error.message);
    res.status(500).send("Internal Server Error");
  }
};

export default router;

여기서는 ssr 서버에서 어떻게 렌더링하는지 알 수 있다.

  • html 가져오기
  • 영화 목록 fetch 해오기
  • renderMovieItems(정적인 html파일을 return 해주는 자바스크립트 함수) 함수에 fetch한 데이터를 넣어 영화 목록 html을 생성한다.
  • <!--${MOVIE_ITEMS_PLACEHOLDER}--> 부분을 위에서 생성한 html으로 바꿔준다.
  • 특정 라우팅 경로에서 다음과 같이 완성된 html을 보내준다.

SSR+CSR

🙎 가정 : 초기 페이지에 접속하면 영화 목록을 확인하고, 영화를 클릭하면 영화의 상세 정보를 모달로 확인하고 싶어요.

이때, 초기 렌더링 속도를 줄이기 위해 SSR 를 활용하나, 그 이후부터 CSR로 동작했으면 좋겠어요.

폴더 구조

📦src
 ┣ 📂apis
 ┃ ┗ 📜fetchMovies.js
 ┣ 📂client
 ┃ ┣ 📂components
 ┃ ┣ 📂pages
 ┃ ┣ 📜App.jsx
 ┃ ┗ 📜main.js
 ┣ 📂server
 ┃ ┣ 📂routes
 ┃ ┃ ┗ 📜index.js
 ┗ ┗ 📜main.js
 
 client 와 server 폴더를 분리해주었다.

client

client 에서는 리액트에서 한 것 처럼 구현해주면 된다.
이 부분은 첫 렌더링 후 동적인 웹사이트를 구성하기 위해 사용된다.

main

const initialData = window.__INITIAL_DATA__;

hydrateRoot(
  document.getElementById("root"),
  <BrowserRouter>
    <App movies={initialData.movies} movieDetail={initialData.movieDetail} />
  </BrowserRouter>
);

기존에 사용하던 리액트와 다르게 initialData 가 있고, createRoot 대신 hydrateRoot가 있는 것을 확인할 수 있다.

App

function App({ movies, movieDetail }) {
  return (
    <Routes>
      <Route path="/" element={<Home movies={movies} />} />
      <Route
        path="/detail/:id"
        element={<MovieDetail movies={movies} movieDetail={movieDetail} />}
      />
    </Routes>
  );
}

app 에서 다음과 같이 라우팅해주었다.

server

import { StaticRouter } from "react-router-dom/server";

const router = Router();
function getInitialDataScript(movies, movieDetail) {
  return `
    <script>
      window.__INITIAL_DATA__ = {
        movies: ${JSON.stringify(movies)},
        movieDetail: ${JSON.stringify(movieDetail)}
      };
    </script>
  `;
}

async function renderApp(location, res, movies, movieDetail) {
  try {
    const renderedApp = renderToString(
      <StaticRouter location={location}>
//1️⃣ StaticRouter 를 활용하면 서버에서도 클라이언트의 라우팅 구조를 그대로 사용할 수 있다
        <App movies={movies} movieDetail={movieDetail} />
      </StaticRouter>
    );

//...생략
}

router.get("/", async (req, res) => {
    const movies = (await fetchNowPlayingMovieItems()) || [];
    await renderApp("/", res, movies, null);
});
// 2️⃣ 영화 목록을 fetch 후 라우팅에 알맞게 html을 보내준다.

router.get("/detail/:id", async (req, res) => {
//...생략
});
// 3️⃣ 영화 상세 목록도 마찬가지로
export default router;

서버 코드이다. client 에서 사용한 코드를 정적인 html 코드로 바꾼 후
각 라우트 마다 넣어주었다. StaticRouter 를 활용하면 경로에 따른 App rendering 이 가능하다.

이 코드는 어떻게 동작할까?

첫 접속 시 서버에서 렌더링된 html 파일이 전달되고(SSR), 그 이후 인터렉션에서는 CSR 이 작동할것이다.

  • 첫 접속 시 네트워크창

  • 클릭 시 네트워크창

첫 접속하면 서버에서 HTML을 전달하고, 영화의 디테일을 보려고 클릭 시 click 이벤트와 navigate가 실행되어 URL이 변경되고 영화의 디테일을 보여줄 것이다. 그리고, 그 상태에서 새로고침 시 첫 접속이기 때문에 디테일 페이지가 서버에서 생성되어 완성된 HTML을 전달할 것이다.

정리하면 다음과 같다.

  1. 초기 로드 (SSR)
  • 서버에서 HTML 생성하여 전달한다.
  • webpack/server : JSX 트랜스파일링,경로 별칭(alias) 해결, asset 처리 등을 처리해야 한다.
  1. 스크립트 로드
  • HTML에 <script defer src="/static/bundle.js"></script> 포함
  • 이는 webpack/client 설정을 통해 자동 주입된다.
  • 스크립트가 로드되면 하이드레이션이 시작된다.
  1. 하이드레이션 준비
  • window.INITIAL_DATA 통해 서버 데이터를 주입한다.
  • 이 데이터로 클라이언트도 서버와 동일한 상태 유지가 가능하다.
  1. 하이드레이션 실행
  • 리액트가 브라우저의 기존 DOM과 INITIAL_DATA가 넣어진 상태로 생성된 가상 DOM을 비교하며, 일치하면 이벤트 리스너를 붙인다. 만약 일치하지 않는다면 에러가 발생한다.
  1. CSR 전환
  • 이후 모든 인터랙션은 클라이언트에서 처리한다.
  • React가 일반적인 SPA처럼 동작한다.

CSR vs SSR 장단점

코드로 SSR 을 알아보고, CSR 과 SSR 을 함께 사용하는 방법, 그리고 이 둘의 렌더링 방식이 어떻게 다른지 알아보았다.

마지막으로 이 두 방식을 비교해보자.

단, react 에서 CSR,SSR을 할 경우에 대한 예시이다.

CSR이 유리한 경우:

  • 사용자의 인터렉션이 많은 웹
    예: SNS, 웹 메일, 관리자 대시보드
    CSR은 클라이언트에서 자바스크립트를 실행하기 때문에 즉각적인 UI 업데이트 가능하다

  • 서버 비용을 줄여야 할 경우
    정적 파일 제공만 하면 되므로 서버 부하가 감소한다.
    추가로, 정적 파일만으로 구성되어 있어 CDN 배포가 용이하다.

SSR이 유리한 경우:

  • 초기 렌더링 속도가 중요한 경우
    예: 뉴스, 쇼핑몰 상품 페이지
    SSR은 완성된 HTML을 바로 받아볼 수 있어 첫 렌더링 속도가 CSR보다 빠르다.
    CSR은 서버로부터 js를(bundle.js) 초기에 모두 다운받아야 하고, 실행까지 완료되어야 하기 때문에 느리다.

💭 (+) 왜 CSR에서 fetch 하는 것 보다 SSR에서 fetch 하는게 비교적 빠른 경우가 많을까?

  • 물리적 거리
    서버 ↔ API 서버: 보통 같은 데이터센터 내에 위치
    브라우저 ↔ API 서버: 사용자의 위치에 따라 물리적 거리가 멀 수 있다.
  • 네트워크 환경
    서버: 안정적인 고속 네트워크 환경
    브라우저: 사용자의 네트워크 상태에 따라 불안정할 수 있다 (3G, 4G, WiFi 등)
  • SEO 최적화가 중요한 경우
    예: 콘텐츠 중심 웹사이트, 마케팅 페이지
    SSR 의 경우 검색 엔진이 완성된 HTML을 크롤링할 수 있어 SEO에 더 유리하다.
    CSR의 경우에는 크롤링 할 때 초기 html을 크롤링 하기 때문에 불리하다.
    요즘은 Google과 같은 주요 검색엔진이 JavaScript를 실행하여 크롤링할 수 있지만, 모든 검색엔진이 그런 것은 아니며, JavaScript 크롤링은 HTML 크롤링보다 더 많은 리소스와 시간이 필요하다.

요즘은 SSR과 CSR을 함께 사용하는 하이브리드 방식이 많이 활용되고 있다.
Next.js와 같은 프레임워크들은 이러한 렌더링 방식들을 쉽게 구현할 수 있도록 도와주고 있다.

렌더링 방식에는 SSR, CSR만 있는 것이 아니라 SSG(Static Site Generation), ISR(Incremental Static Regeneration) 등 다양한 렌더링 전략들이 있다.

각 페이지나 컴포넌트의 특성을 잘 파악하고, 거기에 맞는 최적의 렌더링 방식을 선택해서 조합하자~~


레퍼런스

0개의 댓글