Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[서버 사이드 렌더링 - 1단계] 빙봉(김윤경) 미션 제출합니다. #23

Merged
merged 7 commits into from
Oct 12, 2024

Conversation

Yoonkyoungme
Copy link
Member

@Yoonkyoungme Yoonkyoungme commented Oct 9, 2024

안녕하세요 월하🌙🌝 만나서 반갑습니다! 리뷰 잘 부탁드려요~!
image

👀 리뷰를 통한 생각 나눔

1. SSR 렌더링 시 초기 렌더링 성능이 왜 유리할까?

SSR이 초기 렌더링 성능에서 유리한 이유는 서버에서 클라이언트의 요청을 처리한 후, 완성된 HTML을 미리 생성해 반환하기 때문입니다. 이 HTML은 페이지의 구조뿐만 아니라 텍스트, 이미지, 스타일 등 필요한 모든 요소를 포함한 완성된 문서로, 브라우저는 이를 받아 별도의 자바스크립트 실행 없이도 즉시 DOM 트리를 생성한 후, 바로 렌더 트리를 만들어 화면에 콘텐츠를 렌더링합니다.

반면, CSR에서는 브라우저가 빈 HTML을 받은 뒤 자바스크립트를 다운로드하고 실행해야만 DOM을 구성할 수 있습니다. 이 자바스크립트는 API 호출을 통해 데이터를 가져온 후, DOM을 동적으로 조작하여 페이지 콘텐츠를 구성합니다. 이러한 과정 때문에 데이터 페칭과 렌더링이 분리되고, 자바스크립트 실행이 완료될 때까지는 화면에 아무것도 표시되지 않기 때문에 초기 로딩 속도가 느려질 수 있습니다.

  • SSR 렌더링 흐름
    image

  • CSR 렌더링 흐름
    image


2. 서버에서 렌더링한 영화 목록을 어떻게 클라이언트에 데이터를 전달하고 브라우저에서는 어떤 작업을 수행할까?

서버는 React의 renderToString함수를 사용하여 React 트리를 HTML 문자열로 변환합니다. 이 때, App 컴포넌트는 서버가 가져온 movies 데이터를 속성으로 받아, 해당 데이터를 기반으로 서버에서 미리 완성된 HTML을 생성합니다.

const renderedApp = renderToString(<App movies={movies} />);

서버는 index.html 파일을 읽어와 renderToString으로 생성된 HTML을 div#root에 삽입합니다. 이 과정에서 서버는 클라이언트가 사용할 영화 데이터를 <script> 태그를 통해 window.__INITIAL_DATA__에 저장하여 직렬화(serialize) 합니다.

const initData = `
  <script>
    window.__INITIAL_DATA__ = {
      movies: ${JSON.stringify(movies)}
    }
  </script>
`;

클라이언트(브라우저)는 서버에서 전송된 HTML을 수신하고, 이를 화면에 즉시 렌더링합니다. 이 때 화면에 표시된 내용은 정적인 HTML로, 사용자가 상호작용할 수 없는 상태입니다. 즉, 화면에 영화 목록이 표시되더라도, 버튼 클릭, 페이지 전환 등의 동작은 아직 불가능합니다.

브라우저가 HTML을 렌더링한 이후, React 자바스크립트 파일이 로드됩니다. 이 때 하이드레이션(hydration) 과정이 시작되는데, 하이드레이션은 서버에서 미리 렌더링된 HTML을 기반으로, React 컴포넌트를 상호작용이 가능한 동적인 상태로 전환하는 과정입니다.

하이드레이션은 React에서 제공하는 hydrateRoot 함수를 사용하여 수행됩니다. hydrateRoot는 브라우저가 이미 화면에 렌더링된 정적인 HTML을 가져와, 해당 구조와 동일한 React 컴포넌트를 로드하고 이를 활성화하는 역할을 합니다.

import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById("root"), <App movies={movies} />);

이 과정에서 React는 서버에서 렌더링된 DOM과 클라이언트 측에서 로드된 React 트리의 구조를 비교하여 DOM을 다시 그리는 대신 이벤트 핸들러와 같은 동적 동작을 활성화합니다. 이를 통해 버튼 클릭과 같은 사용자 상호작용이 가능해집니다.

클라이언트 측에서는 서버에서 전달된 초기 데이터를 window.__INITIAL_DATA__로부터 받아와, React 컴포넌트에 이를 전달합니다. 이로 인해 클라이언트는 별도로 데이터를 다시 요청할 필요 없이, 서버에서 전달받은 데이터를 그대로 사용하여 페이지를 활성화할 수 있습니다.

const { movies } = window.__INITIAL_DATA__;

이 데이터는 React 앱의 초기 상태로 설정되며, 서버에서 받은 HTML과 데이터가 정확히 일치하므로 추가적인 데이터 요청 없이 빠르게 상호작용 가능한 상태로 전환됩니다.


3. 전역 객체 초기화 작업은 왜 진행해야 할까?

전역 객체 초기화 작업은 서버와 클라이언트 간의 일치성을 유지하고, 상호작용을 원활하게 하기 위해 필요한 것 같습니다.

미션을 구현하면서 아래와 같은 오류를 만났습니다.

Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

image

해당 에러가 발생했던 이유는 서버에서 렌더링된 HTML과 클라이언트 측에서 React가 다시 렌더링하는 결과가 서로 다르기 때문입니다.
서버 측에서 데이터를 받아 렌더링할 때는 movies 데이터가 포함되어 있지만, 클라이언트 측에서 React가 처음 로드되었을 때는 movies에 대한 초기 데이터가 없습니다. 그래서 클라이언트와 서버에서 다른 렌더링 결과를 낳게 되고, 위와 같은 에러를 발생합니다.

@Yoonkyoungme Yoonkyoungme changed the base branch from main to yoonkyoungme October 9, 2024 15:02
@Yoonkyoungme Yoonkyoungme self-assigned this Oct 9, 2024
@woowahan-cron woowahan-cron added the 🥎 크론 확인 크론이 확인한 경우 레이블을 지정합니다. label Oct 10, 2024
Copy link

@vi-wolhwa vi-wolhwa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM👍

빙뱅 안녕하세요~!
제 코드랑 비교해보면서 리뷰를 해봤는데 너무너무 완벽해서 어디 나무랄 데가 없네요..🤭
앞으로 마지막 개인 미션과 팀 미션, 런칭데이를 앞두고 많이 바쁠텐데, 미션 코드를 깔끔하게 작성해주셔서 놀랐네용 고생하셨어요!
2단계 미션도 화이팅하시구, 남은 기간 동안에도 힘내도 함께 수료해봐요!
고생하셨습니다 빙뱅!🍀

Comment on lines +10 to +12
<Header bestMovie={movies[0]} />
<MovieList movies={movies} />
<Footer />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴포넌트 분리 👍

@vi-wolhwa vi-wolhwa merged commit a13525f into woowacourse:yoonkyoungme Oct 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🥎 크론 확인 크론이 확인한 경우 레이블을 지정합니다.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants