해커톤 코드가 만든 재앙과 리라이팅 구원계획

Overview

창업 프로젝트 AllClass의 전반적인 성능 문제를 발견하고 리라이팅을 결심하게 된 과정을 정리했다. 해커톤에서 만든 코드의 문제점들을 분석하고, 왜 리팩토링이 아닌 리라이팅을 선택했는지, 그리고 앞으로의 계획을 기록해둔다.

부산 지역 대학생들을 위한 강의 리뷰 플랫폼인 AllClass(구 SLR)를 리팩토링 리라이팅하기로 했다. 약 1년 전 해커톤에서 'SLR(Sharing Lecture Review)'로 시작된 이 프로젝트는 MVP라는 명목 하에 최소한의 기능만으로 운영되어 왔고, 이제 AllClass로 리브랜딩하며 전면 개선에 나서게 되었다.

기존 코드의 문제점

모든 페이지가 getServerSideProps를 사용하여 매 요청마다 서버 렌더링되고 있었다.

기존 사이트 성능 측정 결과

기존 배포 사이트: https://allclass.vercel.app

Google Lighthouse 평균 지표만 보면 당장 치명적이라고 단정하긴 어렵다(FCP≈2.1s, LCP≈2.5s). 다만 문제는 일관성 이었다. LCP가 최대 8초까지 치솟는 사례가 반복적으로 관측됐고, 이는 모든 페이지를 getServerSideProps로 동적 렌더링하면서 매 요청마다 네트워크 지연, 서버 콜드스타트, DB 호출 비용이 누적된 탓으로 보인다. 특히 강의 상세 페이지처럼 데이터 의존성이 큰 화면에서는 TTFB 변동이 커지며 사용자 체감 속도가 크게 악화됐다. 아래는 당시 구조에서 요청마다 수행되던 작업이다.

// 매 요청마다 실행되는 비효율적인 구조
export async function getServerSideProps(context) {
  const { lectureId, universityName } = context.query;
  
  const [lectureInfo, reviewDetail, chartData] = await Promise.all([
    fetchLectureInfo(universityName, lectureId),  // 강의 기본 정보
    fetchReviewDetail(lectureId),                // 리뷰 목록
    fetchKeyword(lectureId),                     // 차트 데이터
  ]);
  
  return { props: { lectureInfo, reviewDetail, chartData } };
}

lectureInfo는 학기 중 거의 변경되지 않는 준정적 데이터인데 매번 서버에서 조회하고 있었다. 강의 기본 정보(강의명, 교수명, 시간표 등)의 특성상 정적 생성이 적합함에도 모든 페이지가 동적 렌더링되고 있는 구조였다.

렌더링 전략 재검토

문제의 핵심은 모든 페이지에 getServerSideProps를 적용한 것이었다. Next.js는 정적 생성을 우선 권장하는데, 빌드 타임에 HTML을 생성하면 CDN을 통한 빠른 배포가 가능하고 서버 부하도 줄일 수 있기 때문이다.

"We recommend using Static Generation whenever possible because your page can be built once and served by CDN" — Next.js 공식 문서

강의 정보는 한 학기 동안 거의 변하지 않는 특성을 가지고 있어 정적 생성에 최적화된 데이터였다. 하지만 기존 코드는 이런 정적 데이터까지 매번 서버에서 렌더링하고 있었다.

리팩토링에서 리라이팅으로

성능 문제를 해결하기 위해 기존 코드를 부분적으로 수정할지, 아니면 처음부터 다시 작성할지 고민했다. 결국 리라이팅을 선택한 이유는 다음과 같다.

아키텍처 전체의 문제: 렌더링 방식과 데이터 페칭 전략이 근본적으로 잘못 설계되어 있었다. getServerSideProps를 전면 제거하려면 페이지 구조와 API 레이어를 거의 전부 뜯어고쳐야 했기 때문에, 사실상 리라이팅과 다름없는 작업이 될 상황이었다.

작은 코드베이스: 해커톤에서 시작된 프로젝트라 전체 코드 규모가 크지 않았다. 새로 작성하는 비용이 기존 코드를 하나씩 수정하는 것보다 합리적이었다.

명확한 개선 방향: Next.js의 렌더링 최적화 방법이 명확했고, 어떤 페이지에 어떤 전략을 적용할지 구체적인 계획이 있었다.

기존 레거시 코드: GitHub 링크

새로운 목표 설정

그래서 이번 리라이팅에선 다음과 같은 목표를 세웠다.

목표 1: 렌더링 전략 최적화

기존의 핵심 페이지 모두가 getServerSideProps를 사용하던 구조에서 다음과 같이 변경한다:

  • 랜딩 페이지(/) → SSG (랜딩 페이지의 정보는 거의 변경되지 않음)
  • 강의 목록 페이지(/[universityName]) → ISR (강의 목록은 주기적으로 업데이트되지만 실시간일 필요는 없음)
  • 강의 상세 페이지(/[universityName]/[lectureId]) → 하이브리드 캐싱 전략: 강의 기본 정보는 ISR로 정적 생성하고, 동적으로 변하는 평점과 리뷰 데이터는 클라이언트에서 React Query를 통해 별도 관리

목표 2: 안정적인 리라이팅 환경 구축
리라이팅은 한 번에 코드가 크게 바뀌므로, 회귀 테스트 없이는 기존 기능이 제대로 동작하는지 확신하기 어렵다. 따라서 다음과 같은 안전장치를 마련한다:

  • Cypress를 활용한 E2E 테스트로 핵심 사용자 플로우 검증
  • API 응답 모킹을 통한 안정적인 테스트 환경 구축
  • 기존 백엔드 API 호환성 유지 (API 변경 없이 진행)
  • CI/CD 파이프라인에 테스트 자동화 통합

마무리

이렇게 분석한 문제점들을 바탕으로 본격적인 리라이팅을 시작한다. 각 렌더링 전략을 단계별로 적용하며 실제 성능 개선 효과를 확인해 나갈 예정이다.

해커톤에서 급하게 만들었던 코드가 이제야 제대로 된 서비스의 모습을 갖춰가게 될 것 같다.