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

[서버 사이드 렌더링 - 2단계] 마루(박규한) 미션 제출합니다. #61

Merged
merged 19 commits into from
Oct 24, 2024

Conversation

rbgksqkr
Copy link

@rbgksqkr rbgksqkr commented Oct 21, 2024

안녕하세요 다르~! 두번째 만나네요 이번에도 잘부탁드립니다 😁
구현하는 데에는 크게 오래 안걸렸는데 고민되는 부분들이 몇가지 있어 질문이 좀 있네용 얼마 안남았으니 힘냅시다 🔥🔥🔥🔥🔥🔥

👀 리뷰를 통한 생각 나눔

1. SSR 환경에서 웹 브라우저는 어떤 과정으로 하이드레이션(hydration)을 진행하는지 상세히 서술하시오.

💡 작성 요령: 서버에서 렌더링된 HTML과 클라이언트 측에서 React로 다시 그려질 때 발생할 수 있는 차이점은 무엇이며, 이로 인해 생길 수 있는 문제는 어떤 것이 있는지 고민해 보세요.

  1. 사용자가 브라우저에 접근하면 SSR 서버에 HTML 파일을 요청한다.
  2. SSR 서버는 API 서버에 데이터를 요청한 후, 받은 데이터가 그려진 HTML 파일을 브라우저에 응답한다.
  3. 브라우저는 응답받은 HTML을 파싱하면서 link 태그의 css 코드, script 태그의 js 코드를 다운로드 받아 실행한다.
  4. 다운로드 받은 js 코드를 실행하면서 DOM 트리에 이벤트 리스너 등이 등록되는 하이드레이션이 진행되어 상호작용이 가능해진다.

2. 다음 두 가지 상황의 동작 과정을 비교해 설명하시오.

💡 작성 요령: 웹 브라우저는 처음에 서버에 요청할 때와 영화 목록을 요청할 때 반환 받는 값에는 어떤 차이가 있나요?

사용자가 처음 영화 목록 페이지에 접속할 때는 HTML 데이터를 받고, 하이드레이션 후 상세 페이지로 이동 시에는 JSON 데이터를 받습니다.

사용자가 처음 영화 목록 페이지에 접속할 때

  1. 사용자가 처음 영화 목록 페이지에 접속할 때는 정적인 HTML 을 응답받는다.
  2. HTML 을 다운로드하면 HTML을 파싱하면서 css와 js를 불러온다.
  3. 다운로드 받은 js 코드를 실행하면서 DOM 트리에 이벤트 리스너 등이 등록되는 하이드레이션이 진행되어 상호작용이 가능해진다.

하이드레이션 후, 사용자가 영화 목록에서 특정 영화를 클릭하여 상세 페이지로 이동할 때

  1. 영화 목록에서 특정 영화를 클릭하여 상세 페이지로 이동하는 건 react-router-dom 에 의해 CSR로 페이지를 이동시키는 경우다.
  2. 이때는 경로에 필요한 페이지 단위 컴포넌트를 router가 갈아끼워주는 것처럼 동작하기 때문에 리로드가 발생하지 않는다.
  3. 따라서 이때는 HTML이 아닌 JSON 데이터 만 응답받아 데이터만 갈아끼워주는 작업이 이뤄진다.
router.get("/:movieId", async (req, res) => {
  const movieId = req.params.movieId;

  const movieInfo = await fetchMovieDetail(movieId);
  const movieDetail = parseMovieDetail(movieInfo);

  res.send(movieDetail);
});

3. 다음 두 가지 상황의 동작 과정에서 라우팅의 차이점이 무엇인지 서술하시오.

사용자가 https://주소/detail/12345 와 같이 브라우저에 직접 입력한 뒤 페이지로 이동할 때

하이드레이션 후, 사용자가 영화 목록에서 특정 영화를 클릭하여 상세 페이지로 이동할 때

💡 작성 요령: 각 상황에서 요청을 처리하는 주체가 무엇인지 생각해 보세요.

사용자가 브라우저에 직접 입력할 때는 SSR 서버에 특정 영화 데이터가 포함된 HTML 파일을 응답받습니다. API 서버에 데이터를 요청하고, 브라우저에 HTML 파일을 응답하는 것은 SSR 서버입니다.

반면, 하이드레이션 이후 상세 페이지 이동은 JSON 데이터만 응답받아 컴포넌트 데이터를 갈아끼웁니다. 브라우저에서 SSR 서버에 요청하면 파싱된 상세 페이지 데이터만 JSON 데이터로 응답합니다. 이때 라우팅을 처리하는 것은 브라우저(리액트)고, API 요청과 JSON 데이터 응답하는 것은 SSR 서버입니다.

질문

1. 영화 상세보기 페이지 모달에서 같은 영화를 클릭했을 때 동작 오류

모달 띄워진 다음 다른 영화를 클릭했을 때는 동작하는데, movieId가 같은 영화를 클릭하면 동작하지 않고 있어요. MovieDetailPage의 useEffect 분기처리 문제인 것 같은데, 이를 어떻게 처리했는지 궁금합니다!

2. window 객체 공유를 위한 스크립트 중복

서버 사이드에서 홈화면을 렌더링하는 함수랑 모달을 렌더링하는 함수를 분리했는데 이랬더니 스크립트 태그가 2개 생기는 문제가 생기더라구요. 해결하는 방법은 renderMovieHome에서 스크립트 태그를 분기처리하는 방식인데, 이럴 경우 렌더링 함수를 분리하는 의미가 없어지는 것 같아요. 어떻게 분리하는 게 좋을까요?

3. 클라이언트 코드에서 API 요청

저는 모달을 띄울 때 클라이언트단에서 SSR 서버에 movieId를 넘겨주고, SSR 서버에서 API 서버에 요청을 보내는 형식으로 구현하였습니다.

고민했던 부분은 클라이언트에서 영화 상세 데이터를 응답받을 때 SSR 서버에 요청을 할지API 서버에 직접 요청을 할지 입니다. 저는 2번째 방식에서 1번째 방식으로 바꿨는데요! 필요한 데이터만 파싱하는 게 SSR 서버의 역할 중 하나라 생각했고, 서버 코드를 import 할 수 없는 환경의 경우 변경에 유연하게 대처하기 어려울 것 같다는 생각이 들었습니다. 이에 대한 다르의 생각이 궁금합니다!

@rbgksqkr rbgksqkr self-assigned this Oct 21, 2024
@woowahan-cron woowahan-cron added the 🥎 크론 확인 크론이 확인한 경우 레이블을 지정합니다. label Oct 21, 2024
Copy link

@pp449 pp449 left a comment

Choose a reason for hiding this comment

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

안녕하세요 마루! 코드를 읽어보니 고생한 흔적이 보이네요 ㅎㅎ
로컬로 받아서 실행해보니 정상적으로 동작하는것도 확인했어요!

다만 모달 페이지를 처음 접근한 후 모달창을 닫으면 URL이 그대로 남아있더라구요
image

제가 생각했을때는 영화 목록 페이지, 모달 페이지는 각각 다른 페이지로 생각이 들었고, 하이브리드 렌더링으로 처리하는 경우 모달이 닫히면 URL이 변경되면서 영화 목록 페이지로 이동되는게 자연스럽다고 생각을 해요. 그 이유는 모달을 닫은 후 새로고침을 하면 다시 모달페이지로 가는게 자연스러운 흐름은 아니라고 생각해요

1. 영화 상세보기 페이지 모달에서 같은 영화를 클릭했을 때 동작 오류

다른 크루들의 코드를 보니 HTML파일에 정의한 INITIAL_DATA 의 movieId값이 달라진 경우 useEffect를 이용하여 데이터 패칭을 처리하도록 되어있더라구요!
저는 INITIAL_DATA가 한 번 사용이 되면 그 다음은 INITIAL_DATA를 사용하지 않고 다 서버로 데이터 요청하도록 처리했어요.
첫 접속시에는 서버에서 패칭된 데이터이기에 fresh 하다고 생각했고, 그 다음 동일한 컴포넌트가 다시 렌더링 되는 경우 데이터가 stale 하다고 판단하여 이런식으로 했어요 (살짝 Nextjs의 getServerSideProps 의 동작과 비슷하게 처리해보고 싶었어요)

2. window 객체 공유를 위한 스크립트 중복

저는 스크립트 태그를 객체라 생각하고 하나의 태그에 모든 데이터를 넣어줬어요,, ㅎㅎ

마루가 생각하기에 INITIAL_DATA 의 용도는 무엇이라 생각하나요??
어떻게 보면 INITIAL_DATA SSR 시 서버가 패칭한 결과값을 저장하는데 사실 모두 HTML 엘리먼트에 반영되어 클라이언트에 전달해주기에 언뜻보면 필요없다고 생각이 들 수도 있을거 같은데 마루의 생각이 궁금하네용

3. 클라이언트 코드에서 API 요청

아하 서버에 /:movieId가 있는 이유가 클라이언트에서 TMDB가 아닌 우리 서버를 프록시 서버로 쓰기 위해 사용되는거군요!

제가 생각하기에 이건 요구사항에 따라 다를거 같은데 우선 TMDB는 외부 서버이기에 클라이언트에서 TMDB로 바로 요청하여 처리하는게 대체로 좋을거 같아요!

그 이유는 우리 서버를 프록시 서버로 사용한다면 서버의 부하가 커질거고, 동일한 데이터 2중 요청이기에 불필요한 네트워크 트래픽 증가 그리고 TMDB 서버, 우리 서버 둘 중 하나라도 문제가 생긴다면 클라이언트측에 피해가 예상이 되며 디버깅이 조금 힘들어질 수 있다고 생각해요.

하지만 지금 마루가 해준 방식처럼 우리 서버를 프록시 서버로 사용하는 경우는 언제가 있을지 생각해본다면

  1. BFF 또는 MSA로 구성되는 경우 요구사항에 따라 SSR 서버로 요청하는게 더 적합할 수도 있다고 생각해요
  2. 만약 클라이언트측에서 TMDB 서버로 데이터 요청하기에 권한이 없는경우 (예를들어 CORS 문제가 발생하는 경우) SSR 서버에서 TMDB 서버의 응답을 받아 클라이언트에게 전달해주는 경우 사용할 수 있을거 같아요! - 그런데 이건 법적 문제도 있을거 같으니 알아서 처신해야해요.. ㅋㅋㅋㅋㅋㅋ

<title>영화 리뷰</title>
</head>
<body>
<div id="root"></div>
<!--${MODAL_AREA}-->
</body>
<!--${INIT_DATA_AREA}-->
<!--${INIT_MODAL_DATA_AREA}-->
Copy link

Choose a reason for hiding this comment

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

저는 INIT_DATA_AREA 이쪽에 모든 데이터를 다 넣어줬는데 마루는 각각의 데이터마다 따로 값을 넣어줬네요!
혹시 이렇게 한 이유가 있나요??

Copy link
Author

Choose a reason for hiding this comment

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

함수를 분리하려다보니 이런 구조가 만들어진 것 같아요!
하나의 스크립트 내에서 2개의 데이터를 넣어주려면 renderMovieHome에서 분기처리하거나 함수로 안묶고 라우터단에서 처리하는 방법이 있을 것 같은데, 관심사 분리를 하고 싶어서 이렇게 처리했던 것 같아요 ㅎㅎ

Comment on lines +17 to +24
router.get("/:movieId", async (req, res) => {
const movieId = req.params.movieId;

const movieInfo = await fetchMovieDetail(movieId);
const movieDetail = parseMovieDetail(movieInfo);

res.send(movieDetail);
});
Copy link

Choose a reason for hiding this comment

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

여기는 데이터를 확인하기 위해 사용되는건가요??

Copy link
Author

Choose a reason for hiding this comment

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

SSR서버를 거쳐 API서버에 데이터를 요청할 때 사용하는 라우터입니다!
다르의 말을 빌리면 프록시 역할을 하는 라우터라고 생각하시면 될 것 같아요. 브라우저가 CSR로 데이터를 페칭할 때 SSR 서버의 해당 라우터에 요청을 보내 JSON 데이터를 받습니다. PR에도 작성한 것처럼 원하는 데이터만 파싱하는 과정을 SSR서버에서 해주기 때문에 브라우저는 정제된 데이터만 받을 수 있습니다!
API 서버 응답값의 필드명이 바뀌었을 때 대응하기 쉽다는 장점도 있겠네용

Copy link
Author

@rbgksqkr rbgksqkr left a comment

Choose a reason for hiding this comment

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

다르 꼼꼼한 답변 감사합니다:) navigate로 다시 돌아가는 생각 좋은 것 같아요! 이전 미션에서처럼 유지되는 게 요구사항이라고 생각해서 유지했는데, 돌아가는 게 자연스럽기도 하고 같은 영화를 클릭했을 때 생긴 문제도 해결되었네요!!!

마루가 생각하기에 INITIAL_DATA 의 용도는 무엇이라 생각하나요??

엇 하이드레이션 하기 위해선 필요하지 않나요?? 서버에서 window 객체로 넘겨준 데이터로 클라이언트(리액트)가 서버에서 넘겨준 HTML과 같은 트리구조를 그리기 위함이라고 생각합니다!
하이드레이션 이후에는 클라이언트단에서 다양한 로직을 처리하기 위해 넘겨준 데이터를 상태로 관리하여, 다양한 상호작용을 가능하게 만들 수 있을 것 같습니다!

다르 말대로 SSR서버로 보낼지 API 서버로 보낼지는 상황에 따라 다를 것 같긴 합니다! 여러 요구사항들을 따져봐야겠네요 감사합니다 😁😁😁

Comment on lines +17 to +24
router.get("/:movieId", async (req, res) => {
const movieId = req.params.movieId;

const movieInfo = await fetchMovieDetail(movieId);
const movieDetail = parseMovieDetail(movieInfo);

res.send(movieDetail);
});
Copy link
Author

Choose a reason for hiding this comment

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

SSR서버를 거쳐 API서버에 데이터를 요청할 때 사용하는 라우터입니다!
다르의 말을 빌리면 프록시 역할을 하는 라우터라고 생각하시면 될 것 같아요. 브라우저가 CSR로 데이터를 페칭할 때 SSR 서버의 해당 라우터에 요청을 보내 JSON 데이터를 받습니다. PR에도 작성한 것처럼 원하는 데이터만 파싱하는 과정을 SSR서버에서 해주기 때문에 브라우저는 정제된 데이터만 받을 수 있습니다!
API 서버 응답값의 필드명이 바뀌었을 때 대응하기 쉽다는 장점도 있겠네용

<title>영화 리뷰</title>
</head>
<body>
<div id="root"></div>
<!--${MODAL_AREA}-->
</body>
<!--${INIT_DATA_AREA}-->
<!--${INIT_MODAL_DATA_AREA}-->
Copy link
Author

Choose a reason for hiding this comment

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

함수를 분리하려다보니 이런 구조가 만들어진 것 같아요!
하나의 스크립트 내에서 2개의 데이터를 넣어주려면 renderMovieHome에서 분기처리하거나 함수로 안묶고 라우터단에서 처리하는 방법이 있을 것 같은데, 관심사 분리를 하고 싶어서 이렇게 처리했던 것 같아요 ㅎㅎ

Copy link

@pp449 pp449 left a comment

Choose a reason for hiding this comment

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

빠르게 리뷰반영까지 다 해줬군요 마루!

하이드레이션 하기 위해선 필요하지 않나요?? 서버에서 window 객체로 넘겨준 데이터로 클라이언트(리액트)가 서버에서 넘겨준 HTML과 같은 트리구조를 그리기 위함이라고 생각합니다!

앗 맞아요 INITIAL_DATA를 어떻게 활용할 수 있을까를 생각하다보니 첫 렌더링 시 하이드레이션 하기위해 사용된다는 것을 잠시 까먹고 있었네요.. ㅎㅎ

요구사항을 모두 충족하고, 전체적으로 잘 만들어주어 approve 남기도록 할게요
마지막 미션까지 고생했어요 내일 최종 데모데이까지 화이팅~! 😄

@pp449 pp449 merged commit a30b279 into woowacourse:rbgksqkr Oct 24, 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