1. 문제 상황
기존 서비스는 CRA(Create React App) 기반의 CSR 구조로 운영되고 있었다.
초기에는 빠른 개발과 단순한 구조 덕분에 큰 문제가 없었지만, 서비스가 성장하면서 점점 한계가 드러났다.
특히 다음과 같은 문제가 반복적으로 발생했다.
- 초기 로딩 속도 지연 (FCP/LCP 저하)
- SEO 대응의 어려움
- 번들 사이즈 증가
- Webpack 기반 빌드 속도 저하
단순한 최적화로는 해결이 어려웠고,
결국 렌더링 방식 자체를 바꾸는 방향을 검토하게 되었다.
2. 기존 구조의 한계
2.1 CSR 구조의 구조적 문제
CRA 기반 CSR에서는 모든 렌더링이 클라이언트에서 이루어진다.
이로 인해:
- HTML은 비어있는 상태로 전달됨
- JS 로딩 이후에야 화면 렌더링
- 초기 사용자 경험 저하
특히 네트워크 환경이 좋지 않을수록 문제는 더 크게 체감되었다.
2.2 Webpack 기반 개발 경험의 한계
프로젝트 규모가 커지면서 개발 환경에서도 문제가 발생했다:
- 개발 서버 시작 속도 저하
- HMR 속도 저하
- 빌드 시간 증가
개발 생산성 자체가 점점 떨어지는 상황이었다.
3. 해결 방향
다음 두 가지를 핵심 목표로 설정했다:
- 초기 렌더링 성능 개선 → SSR 도입
- 개발 경험 개선 → Vite 도입
4. Vite + SSR 선택 이유
4.1 Vite
- 빠른 개발 서버 시작
- 즉각적인 HMR
- ES Module 기반 구조
→ 개발 경험이 크게 개선됨
4.2 SSR
- 초기 HTML에 콘텐츠 포함
- 빠른 첫 렌더링
- SEO 대응 가능
→ 사용자 체감 성능 개선
5. 마이그레이션 과정
5.1 구조 변경
기존:
- CRA (CSR)
변경:
- Vite (빌드/개발 환경)
- Node.js 기반 SSR 서버
5.2 라우팅 처리
- 서버에서 route 매칭
- 초기 데이터 fetch
- HTML에 상태 주입
5.3 상태 동기화
- 서버에서 데이터 fetch
- serialize → HTML에 포함
- 클라이언트에서 hydrate
5.4 Hydration 이슈
SSR 도입 시 가장 많이 겪는 문제:
원인
- 서버/클라이언트 렌더링 결과 불일치
- 브라우저 전용 API 사용
해결
useEffect로 클라이언트 로직 분리- 조건부 렌더링
- 랜덤/시간 기반 값 제거
6. 성능 개선 결과
- 초기 렌더링 속도 개선
- 체감 성능 향상
- SEO 대응 가능
특히 “빈 화면 → 렌더링” 구조가 사라진 것이 가장 큰 변화였다.
7. 트러블 슈팅: Event Loop Lag 문제
SSR 도입 이후 또 다른 문제가 발생했다.
바로 Node.js Event Loop Lag 증가였다.
7.1 문제 현상
- API 응답 속도 지연
- 서버 CPU 사용량 증가
- SSR 렌더링 시간 불안정
특히 트래픽이 몰리는 구간에서 심각하게 나타났다.
7.2 원인
SSR에서는 요청마다 서버에서 렌더링이 수행된다.
이 과정에서:
- React 렌더링 (renderToString)
- 데이터 가공 로직
- 동기 처리
CPU 바운드 작업이 Event Loop를 점유
7.3 문제의 본질
Node.js는 싱글 스레드 기반이기 때문에
하나의 요청이 오래 걸리면 전체 처리에 영향을 준다.
SSR 환경에서는 이 문제가 더 쉽게 드러난다.
7.4 해결 방법
1. 렌더링 최적화
- 불필요한 렌더링 제거
- 메모이제이션 적용
2. 데이터 처리 분리
- SSR에서는 최소 데이터만 사용
- 무거운 로직은 별도 처리
3. 캐싱 도입
- SSR 결과 캐싱
- 데이터 캐싱
4. 비동기 처리 개선
- 동기 로직 제거
- 비동기 처리로 전환
7.5 개선 결과
- Event Loop Lag 감소
- 응답 속도 안정화
- 트래픽 상황에서도 성능 유지
8. 느낀 점
- 성능 문제는 단순 최적화로 해결되지 않는다
- 렌더링 방식 변경이 더 효과적인 경우가 많다
- SSR은 강력하지만 복잡도를 증가시킨다
- Node.js 환경에서는 Event Loop 이해가 필수다
9. 이런 경우에 추천
- 초기 렌더링 성능이 중요한 서비스
- SEO가 필요한 서비스
- CRA 기반에서 한계를 느끼는 경우
10. 마무리
CRA에서 Vite + SSR로의 전환은
단순한 도구 변경이 아니라
애플리케이션 구조 자체를 재설계하는 작업이었다.
초기 비용은 있지만,
장기적으로는 충분한 가치가 있다고 판단된다.
'Develope > React' 카테고리의 다른 글
| Zustand Selector는 언제 써야 할까? 성능과 가독성 사이에서 판단하는 기준 (1) | 2026.04.09 |
|---|---|
| Recoil - Cannot update a component (`Batcher`) while rendering a different component 에러에 대하여 (2) | 2020.07.02 |
| [React]반복문으로 컴포넌트 렌더링시 고유값을 부여하는 이유 (0) | 2020.06.23 |
| CRA(Create React App) 서비스워커(ServiceWorker) 커스텀 (0) | 2020.06.13 |