당근 테크 블로그

당근은 동네 이웃 간의 연결을 도와 따뜻하고 활발한 교류가 있는 지역 사회를 꿈꾸고 있어요.

입사 1개월차 프론트엔드 개발자의 자체 기술 온보딩 : Streaming SSR편

--

안녕하세요! 커뮤니티실 동네생활팀 프론트엔드 엔지니어 리디아(Lydia)예요. 당근에 합류한 지 이제 막 한 달이 넘어가고 있는 따끈따끈한 저의 경험을 여러분께 공유하고 싶어 찾아왔어요.

당근 동네생활팀은 이웃들과 동네에서 일어나는 다양한 경험과 정보를 공유하고 교류하는 ‘공간’을 만들어 나가고 있어요. 이 과정에서 다양한 실험과 개선을 하기 위해 클라이언트 영역을 네이티브에서 웹으로 전환했어요. 하지만 클라이언트 사이드 렌더링(CSR) 기반 웹앱(Web Application)은 사용자의 모바일 기기 성능이 좋지 않은 경우 또는 웹뷰 로딩이 느린 경우에 따라 네이티브 대비 부정적인 사용자 경험을 줄 가능성이 높아요.

이를 해결하기 위해 동네생활팀은 서버 사이드 렌더링(SSR)을 도입했어요. 그러나 API 응답이 길어질 경우 빈 화면이 노출되는 문제가 새롭게 발생했어요. 저희 팀은 이 문제를 개선하기 위해 현재 Streaming SSR까지 도입한 상태예요.

저는 팀에서 Streaming SSR이 적용된 웹앱을 개발 및 운영하는 역할을 담당하게 되었어요. 처음 접해보는 기술이라 힘들었지만 나름대로 노하우를 쌓아가며 낯선 기술에 빠르게 적응할 수 있었죠.

새로운 팀에 합류하시는 분, 새로운 기술에 적응해야 하시는 분들을 위해, 팀이 사용하는 기술에 적응해 나가는 제 온보딩 과정을 소개하려고 해요. 가벼운 마음으로 재밌게 읽어주세요~! 🐥

히스토리를 이해하자

1. 팀의 기술 도입 배경 이해하기

팀의 기술에 적응하기 위해 가장 중요한 것은 무엇일까요? 그건 바로 ‘히스토리 파악’이에요. 팀이 그 기술을 도입한 배경과 목표를 이해한다면, 기술을 더욱 빠르게 익힐 수 있기 때문이에요. 제가 동네생활팀의 기술 도입 배경을 이해했던 3가지 과정을 소개해 드릴게요.

1️⃣ 도입 맥락 파악

동네생활팀은 회의록과 PRD, 기술 스펙 등 기능 도입 시 필요한 문서들을 노션에 정리해요. 회의록에는 동네생활 웹에서 Streaming SSR을 도입한 배경이 정리되어 있었어요. SSR을 도입하게 된다면 다양한 문제점들이 예상됐기 때문인데요. 예를 들어 상세 게시글에서는 SSR로 첫 화면을 렌더링할 경우, API 호출이 오래 걸릴 시 로딩 없이 빈 화면이 사용자에게 노출되는 문제가 발생해요. 팀에서는 해당 문제를 해결하기 위해 Streaming SSR을 도입하여 API 호출 필요 여부에 따라 구간을 분리하여 FMP*를 개선했어요.

이렇게 도입 이전의 문제점을 기반으로 팀의 히스토리를 파악한 덕에, 작업 시 고려 사항을 다시 한번 확인할 수 있었어요.

*FMP(First Meaningful Paint) : 브라우저가 페이지의 주요 컨텐츠를 화면에 처음으로 렌더링하는 지점

2️⃣ 고민 과정 이해

당근은 Slack을 통해 다른 구성원들과 원활하게 소통하고 있어요. Slack에는 지금까지 Streaming SSR을 적용하면서 생겼던 디바이스 또는 기술적인 문제들을 다른 개발자에게 공유하고 해결하는 과정들이 담겨 있어요.

히스토리를 찾아보되 너무 오랜 시간이 걸린다면 빠르게 전사 메신저를 이용해 해당 내용을 검색해 보세요. 혹시 문서화가 되어 있지 않은 부분을 발견한다면 잘 정리해서 기록으로 남겨두는 것도 팀에 긍정적으로 기여하는 방법이에요!

혹은 메신저에서 해당 논의가 오갔던 스레드 링크를 팀원에게 직접 요청하고 원하는 정보를 얻어내는 것도 방법이에요. 당근은 질문에 대한 두려움을 가진 분들도 편하게 물어볼 수 있는 환경을 제공해요. 투명한 공유 문화를 가진 당근에서는 팀 내의 고민 과정을 빠르게 파악하기 위해 이렇게 전사 메신저를 활용할 수 있죠!

3️⃣ 구현 세부 사항 분석

Github PR을 확인하며, 제가 중점적으로 봐야 하는 코드와 해당 코드가 어떤 기술을 구현하기 위해 사용되었는지를 파악할 수 있었어요. 실제 팀에서 사용하는 코드의 흐름을 따라가며 해당 기술을 이용해 구현한 세부 사항들을 디테일하게 분석해요.

하나의 예시로, Streaming SSR 모듈 분리 PR을 통해 Streaming SSR 구현부 외에도, 개발모드 및 빌드모드에서의 Node 서버 환경 설정 작업을 집중적으로 볼 수 있었어요. 이처럼 새로운 기술 도입과 환경 설정 작업을 집중적으로 봐야 하는 상황에서 Github PR은 큰 도움이 돼요!

2. 프로덕트 관점에서 기술 사용 목적 이해하기

히스토리와 개념을 알았다면 다음으로 중요한 것은 무엇일까요? 팀에서 해당 기술을 적용하고 있는 프로덕트에 대한 이해라고 생각해요. 동네생활팀은 오픈게시판, 프로필, 피드를 개발하고 있어요.

팀에서 Streaming SSR을 도입한 이유에 대해 프로덕트의 관점에서 설명해 드릴게요. 동네생활 게시글 상세 페이지의 경우, 사용자의 진입 속도를 높이기 위해 글 본문은 Streaming SSR로, 댓글과 답글은 CSR로 적용하고 있어요.

사용자가 상세 게시글을 볼 경우 진입 시점 페이지에 보이지 않는 기능은 서버에서 불러올 필요가 없어요. GNB와 같이 API 호출이 필요 없는 부분을 먼저 띄워주고 API 호출이 필요한 부분은 suspense의 fallback 속성을 사용하여 로딩 중에 로딩 스피너를 출력시켜 줘요. 로딩 스피너를 사용하면 페이지가 실제로 로드되고 있다는 느낌을 줄 수 있어요. 대기 시간을 더 견딜 수 있게 만들어 사용자 경험을 높이는 거죠.

이처럼 단순히 기술에 대한 개념뿐만 아니라 이 기술을 사용하는 목적을 알면 기술에 대한 이해도와 활용도를 높일 수 있어요.

구체적인 활용 방식을 익히자

1. 기본적인 개념 익히기

Streaming SSR을 처음 접하면서 구체적으로 이 기술은 어떻게 구현되며, 또 어떤 방식으로 동작하는지 궁금했어요.

참고자료 : Patterns.dev https://mxstbr.com/thoughts/streaming-ssr/
(참고자료 Patterns.dev https://mxstbr.com/thoughts/streaming-ssr/)

생소한 기술이다 보니 공식 문서를 기준으로 개념부터 시작하여 동작 원리를 파악했어요. 제가 참고한 공식 문서는 renderToPipeableStream, vite ssr, patterns.dev 등이 있어요. 간략하게 요약하면, SSR은 서버에서 전체 HTML을 생성한 후 전송하고, Streaming SSR은 HTML을 Chunk 단위로, 부분적으로 스트리밍하여 전송해요. Streaming SSR을 사용하면 중요한 콘텐츠를 먼저 사용자에게 전달할 수 있어 전체 페이지가 모두 로드되기 전에 사용자에게 콘텐츠를 보여줄 수 있어요. 이로 인해 더 빠른 초기 로딩 시간을 제공하고, 더 나은 사용자 경험을 제공한다는 장점이 있어요.

좀 더 응용의 부분에서 궁금증이 생긴다면 이 기술을 사용하고 있는 다른 개발자의 글이나 영상도 참고할 수 있겠죠? 운 좋게 제 버디인 로토(Roto)가 인프콘에서 발표한 자료가 저에게는 큰 도움을 주었어요. Streaming SSR 도입을 고민 중이거나 Streaming SSR이 궁금하다면, 한 번씩 봐주셔도 좋을 것 같아요!

  • 로토의 Streaming SSR 관련 영상 소개

2. 직접 구현해 보기

제가 투입된 프로젝트는 이미 SSR, Streaming SSR 자체 구현 시 생기는 세팅 문제들을 거의 해결한 상태였어요. 하지만 제가 초기 개발 과정에 참여하지 않았다 보니, 버그나 이슈가 생길 경우 기능적인 문제인지 구조적인 문제인지 파악하기 어려운 경우가 있었어요. 그래서 저는 간단한 프로젝트를 만들어 SSR, Streaming SSR을 직접 구현해 보기로 결정했어요. 기술 스택은 동네생활팀에서 사용하고 있는 기술을 이용해요!

그러면 먼저, 어떻게 하면 모르는 부분만 짚어서 빠르게 구현할 수 있었는지 소개해 드릴게요.

1️⃣ 코드 구조 파악

  • 먼저, Streaming SSR이 도입된 회사 코드를 검토해요.
  • 코드를 읽기 쉽도록 기능적인 부분을 최대한 덜어내고 간략한 코드 형태로 정리해요.
  • 주요 흐름과 구조를 파악해요.

2️⃣ 핵심 포인트 식별

  • Streaming SSR의 핵심 개념과 동작 방식을 이해해요.
  • 코드에서 모르는 포인트, 예를 들어 특정 패턴이나 구현 방식을 콕 짚어 파악해요.

3️⃣ 자체 구현 시도

  • 코드 구조와 흐름을 이해한 후, 빈 프로젝트를 생성하여 자체 구현을 해요.
  • 간단한 예제부터 시작하여 복잡한 기능을 구현해요. (CSR → SSR → Streaming SSR)
  • 모르는 포인트는 빠르게 질문하거나 레퍼런스를 찾아 해결해요.
  • 구현 중에 발생하는 문제는 디버깅하고 최적의 해결 방법을 찾아 적용해요. 이 과정에서, Steaming SSR 구현 시 흔히 발생하는 Hydration mismatch error와 같은 문제들을 경험해 볼 수 있어요.

제가 React와 Vite를 사용하여 Streaming SSR을 구현한 예시를 보여드릴게요. 핵심 포인트만 뽑아 작성하여 어렵지 않으니 가볍게 읽어주세요!

1️⃣ 개발 모드에서 Vite를 사용한 서버사이드 렌더링 및 HTML 템플릿 변환

  • 프로덕션 빌드 모드일 경우에는 await vite.ssrLoadModule('/src/entry-server.js') 대신import('./dist/server/entry-server.js') 를 사용하여 SSR 빌드 결과물의 스크립트를 로드하도록 해요.
  • 생성된 HTML 콘텐츠를 템플릿의 <!--ssr-outlet--> 부분에 삽입하여 클라이언트에게 응답해요.

2️⃣ TanStack Query를 사용한 데이터 프리패칭

  • 서버에서 HTML을 렌더링하기 전에 api를 먼저 prefetch하는 기능이에요.
  • 서버에서 데이터를 미리 패칭한 후, TanStack Query의 캐시 상태를 JSON 형태로 직렬화해요.

3️⃣ renderToPipeableStream을 이용하여 HTML 스트리밍 및 오류 처리 구현

  • renderToPipeableStream을 호출하여 React 트리를 HTML로 Node.js 스트림으로 렌더링해요.
  • onShellReady는 shell이 준비되었을 때 동작하고, onAllReady은 전체 HTML이 모두 렌더링되었을 때 호출돼요. 크롤링과 같은 기능을 넣기 위해서는 onAllReady에서 처리해요.
  • onShellError를 통해 shell 렌더링 중 오류가 발생할 경우 스트리밍을 중단하고 CSR로 변경해요.
  • 위와 마찬가지로, Timeout 설정을 통해서도 3초가 지나면 SSR 스트리밍을 중단하고 CSR로 변경해요.

4️⃣ dehydration 된 값을 hydration 해서 클라이언트로 전달

  • 클라이언트에서 hydrateRoot를 호출하여 서버에서 생성된 HTML을 상호작용이 가능하도록 해요.
  • HydrationBoundary 는 클라이언트에서 서버로부터 전달받은 직렬화된 상태를 TanStack Query의 초기 상태로 사용하여, 동일한 데이터를 클라이언트에서 패칭하지 않고 사용할 수 있도록 해요.

이처럼 직접 구현하는 과정에서 실제 발생하는 에러를 디버깅하며, Streaming SSR에 대한 나만의 노하우를 쌓을 수 있어요.

혼자 이해하기 힘든 부분은 팀원에게 도움을 요청하자

1. 두려워하지 말고 질문하기

기술을 스스로 익히다 보면 도저히 혼자서는 이해하지 못하는 부분도 충분히 생길 수 있죠! 단순히 기술에 대한 개념뿐만 아니라 팀이 그 기술을 활용하는 방식, 팀의 업무 방식, 기술 사용 시 자주 발생하는 문제 등도 함께 궁금해질 수 있어요. 그런 의문들을 빠르고 확실히 해소하는 좋은 방법은 그 맥락을 잘 알고 있는 팀원에게 묻는 거예요. 아래는 제가 팀원에게 질문했던 사례들이에요.

유저 인증 과정은 어떻게 동작하나요?

유저 인증 과정의 경우 인증을 위한 토큰 종류부터 토큰 관리를 위해 사용할 스토리지 종류까지 넓은 범위에서 고민해야 할 과제예요. 특히 저는 브릿지를 처음 사용해 봤기 때문에 브릿지를 이용한 인증 과정은 코드로만 이해하기엔 어렵더라고요. 이런 경우 다른 부분에서 시간을 더 유의미하게 사용할 수 있도록 빠르게 팀원에게 질문해도 좋아요!

개발과 빌드과정의 테스트 차이는 무엇인가요?

개발 후 테스트를 진행할 경우 고려해야 하는 상황이 많았어요. 알파 버전/프로덕션 버전, 모바일 환경/브라우저 환경, 개발 모드/빌드 모드 등 제가 고려해야 하는 테스트 환경과 테스트 방식을 확인했어요. 테스트 환경을 세팅하는 건 개발 코드를 이해하기 전에 동작 방식을 확인하기 위해 가장 선행되어야 하는 과제예요. 이 부분에서 시간을 지나치게 소모한다면 다른 팀원에게 빠르게 도움을 요청하여 다음 단계로 가는 것을 추천해요!

애니메이션 문제가 뭔가요?

웹 개발만 해본 저에게는 낯선 표현이었어요. 실제 모바일 환경에서 서로 다른 액티비티 전환이 있으면 transition 효과를 통해 부드럽게 화면이 동작하도록 하는 거였죠. 사실 이 부분은 정말 사소한 건데 이렇게 작은 질문들도 주저 없이 해보시라는 의미에서 적어봤어요!

혼자 이해하기 어렵거나 궁금한 부분이 있을 때는 팀원에게 도움을 요청해 보세요. 현재 하는 고민이 이전에 누군가가 해본 고민일 수 있어요. 고민한 부분과 접근 방법을 공유하고 더 좋은 피드백을 얻어 보세요.

2. 팀원과 스터디를 진행하며 헷갈리는 개념을 명확하게!

팀에 합류하면서 기술에 적응하기 위한 가장 좋은 방법 중 하나는 저와 같은 고민을 하는 동료에게 스터디를 제안해 보는 것이에요. 혼자 해결하지 못한 문제를 같이 풀어나갈 수 있고, 모호한 개념을 명확하게 할 수 있는 큰 원동력이 될 수 있죠. 아래는 Streaming SSR 도입 이유에 대해 스터디를 진행한 내용을 간단히 옮겨 보았어요.

Lydia : 각자가 생각하는 Streaming SSR 도입 이유에 대해 같이 논의해 보면 좋을 것 같아요~!

Suri : 전체 html이 다 내려지기까지 화면이 보여지는 게 좀 느려져서, chunk로 단위를 쪼개서 보여주기 위함이 아닐까요?

Lydia : 모바일에서 유독 느린 것을 체감한다고 생각해요. 네이티브로 되어있던 환경을 웹뷰로 전환하면서 사용자는 네이티브 속도의 기대치를 가지고 있다 보니, 체감상 더 느리다고 생각한 것이 아닐까요?

Suri : 네, 그러다 보니 상대적으로 CSR보다 초기 로딩 속도가 빠른 SSR을 적용하게 된 것 같아요.

Lydia : 저는 실제로 웹뷰에서 API 호출 응답이 길 경우, 로딩바가 보이지 않고 빈 화면만 사용자에게 노출해 주는 경우도 문제가 되었다고 생각해요.

Suri : 그래서 그 문제를 해결하기 위해선 네이티브에서 로딩 스피너 기능을 넣어줘야 해요. SSR의 경우 서버에서 렌더링 된 HTML을 한 번에 내려줘서 로딩 스피너와 같은 일부 UI 요소만을 보여줄 수 없기 때문이에요.

Lydia : Streaming SSR의 경우는 chunk를 이용해서 일부 UI 요소인 로딩 스피너를 먼저 보여줌으로써 사용자에게 화면이 비어있다는 느낌을 주지 않을뿐더러 실제로는 더 빠른 초기 로딩 속도도 구현할 수 있었던 거군요!

이처럼 작은 기능도 실제 동작 원리와 연관 지어서 생각하고 실제 도입 이유를 구체적으로 고민해 볼 수 있어요.

나아가 스터디를 진행한 내용은 다른 동료에게 공유하고 피드백 받아 보는 것도 다음 스텝이 될 수 있어요. 사소하지만 지나칠 수 있는 부분을 공유함으로써 팀의 기술적 역량을 더욱 단단히 다질 수 있을 거라 믿어요.

스스로 개선점을 도출해 보자

1. 스스로 버그를 찾고 직접 해결해 보기!

일반적으로는 시간을 절약하기 위해 자동화 테스트를 돌리지만, 전반적인 프로젝트와 기능 코드를 이해하려면 수동테스트가 빠질 수 없겠죠?

코드를 보면서 예측대로 플로우가 동작하는지 이벤트를 발생시켜 보세요. 만약 그 과정에서 버그를 만난다면? 두려워하지 마세요!

  • 팀에 해당 버그를 공유하고 팀과 논의를 통해 해결해 나가면 좋아요.
  • 스스로 과감하게 해당 문제에 대해서 접근해 보는 것을 추천해요.
  • 만약, 스스로 해결하기엔 너무 어렵다고요? 당근에는 뛰어난 동료들이 함께하기 때문에 문제점을 공유하고 같이 해결해요!

예시로, 서로 다른 웹뷰의 데이터 싱크를 맞추기 위해 여러 솔루션을 제안했어요. 제가 제안한 솔루션 대부분은 댓글 또는 답글 갱신 시 피드 쪽 데이터를 다시 불러오는 refetch 과정이 포함되어 있었어요. 하지만 로토의 피드백을 통해 특정 API의 경우 데이터를 다시 불러올 때 사용자가 보고 있는 피드 내용이 바뀔 수 있다는 문제점을 알게 됐어요. 결국, 제가 제안한 여러 아이디어가 일관성 유지에는 도움이 되지만, 사용자 경험을 저해할 수 있는 문제를 포함한다는 것을 깨달았어요.

동료의 새로운 관점이 더해지니 제가 제시할 수 있는 솔루션이 더욱 뾰족해지더라고요. 덕분에 버그 해결 역량도 기르고 기능 온보딩도 무사히 마칠 수 있었답니다.

2. 다음 타자를 위해 내가 할 수 있는 것은 무엇일까?

SSR, Streaming SSR을 직접 구축하면 절대 빠질 수 없는 게 있어요. 바로 서버와 인프라 관리에요. 모니터링, 테스트, 로그, 배포 등 프론트엔드 개발자지만 신경 써야 할 부분이 많아요.

동네생활팀에서는 안정적인 인프라 구축과 로깅을 위해 Datadog, Sentry와 같은 다양한 툴들을 사용하고 있어요.

개발 문서뿐만 아니라 서버와 인프라 관리 측면에서도 참고할 만한 문서가 정리되어 있으면 좋겠다고 느꼈어요. 저는 서버와 인프라와 관련해 파편화된 문서를 하나로 정리해서 공유할 예정이에요.

글을 마치며

제가 준비한 이야기는 여기까지예요. 🤗

저와 비슷하게 팀에 합류했는데 처음 써보는 기술이 있는 분들, 팀에 좀 더 빠르게 적응하기 위한 방법론이 궁금한 분들께 제 글이 조금이라도 도움이 되었으면 좋겠어요.

동네생활팀이 사용하는 기술은 프로덕트에 진심인 사람들이 모여 고민하고 개선해 나간 과정이 고스란히 담겨있어요. 덕분에 이렇게 기술에 적응하면서 저희 팀이 지금까지 해온 고민과 노력을 더 깊이 이해할 수 있었어요.

앞으로 동네생활팀에서 더욱 성장해 나가는 제 모습을 기대해 주세요. 다음 포스팅에서도 많은 분들께 도움 될 여러 경험과 인사이트를 가져올게요~!

읽어주셔서 감사해요! 💞

--

--

당근 테크 블로그
당근 테크 블로그

Published in 당근 테크 블로그

당근은 동네 이웃 간의 연결을 도와 따뜻하고 활발한 교류가 있는 지역 사회를 꿈꾸고 있어요.

Responses (2)