첫 화면 로딩 3초에서 0.04초로 줄이기 (SSR/SSG/ISR)


우리는 처음에 React 기반으로 앱을 운영하고 있었다.
모든 화면이 클라이언트에서 로딩되는 구조였고, 지금 돌아보면 그 구조를 성능 관점에서 효율적으로 설계하지도 못했다.

그래서 메인 페이지에 기능이 추가될수록 초기 화면 로딩 시간이 매우 길어졌다.
현재 사용 중인 맥북은 시중에 나와 있는 노트북 중에서도 거의 최고 수준의 성능임에도 불구하고, 메인 페이지 로딩에 약 3초가 걸렸다.
일반적인 모바일 환경에서는 5초에서 길게는 8초까지도 소요되었다.

13 Website Page Load Time Statistics (2025 Data)에 따르면, 페이지 로딩 시간이 4초를 넘어가면 유저의 20% 이상이 페이지에 들어오기 전에 이탈한다고 한다.

게다가 초기 로딩 시간은 단순한 속도의 문제가 아니라, “이 서비스가 완성된 서비스인가?” 라는 인상을 좌우하는 요소라고 생각했다.

그래서 첫 화면에서 뭔가 걸리적거리거나 답답한 느낌을 주는 순간, 그 서비스는 이미 첫인상에서 지고 들어간다고 느꼈다.

그래서 React에서 Next.js로 마이그레이션을 진행하면서, 초기 화면 로딩 시간을 극적으로 줄이는 것을 가장 중요한 목표로 삼고 메인 페이지를 재구성하게 되었다.


1. 메인 페이지를 Server Side Rendering (SSR)으로 전환

SSR의 가장 큰 장점은 초기 로딩 속도다.
페이지와 데이터를 가져오는 과정이 클라이언트의 네트워크가 아니라 Next.js가 배포되어 있는 서버의 네트워크를 타게 된다.

즉, 유저의 기기 성능이나 네트워크 상태에 덜 의존하고 누구나 비교적 일정한 품질의 초기 응답을 받을 수 있다는 장점이 있다.

이 특성은 메인 페이지처럼 첫 유저 경험을 담당하는 화면에 매우 적합했다.
그래서 서버에서 캐릭터 데이터 등 메인 페이지에 필요한 모든 정보를 미리 로드한 뒤, hydration 하는 방식으로 페이지를 구성했다.

그 결과, 초기 로딩 시간이 약 3초 → 800ms 수준으로 크게 줄었다.
체감 속도는 확실히 좋아졌지만, 극한의 최적화를 목표로 하고 있던 입장에서는 0.8초 조차 길게 느껴졌다.


2. Static Site Generation (SSG) 으로 전환

그래서 다음으로 떠올린 선택지는 SSG이었다.
빌드 타임에 페이지를 미리 만들어 두고, 유저에게는 그 결과물을 그대로 내려주는 방식이다.

런타임에 데이터를 가져올 필요가 없고 단순히 HTML을 서빙하기만 하면 되기 때문에 이론적으로는 가장 빠른 초기 로딩이 가능하다.

하지만 고려해야 하는 문제가 있었다.
우리 메인 페이지에는 추천 캐릭터를 보여주는 기능이 있다.

  • 비회원 유저에게는 공통 추천 캐릭터
  • 회원 유저에게는 개인화된 추천 캐릭터

Static Generation을 적용하면 페이지는 빌드 타임 기준으로 고정된다.
즉, 인증된 유저와 인증되지 않은 유저가 같은 추천 캐릭터를 보게 되는 문제가 발생한다.

그렇다고 페이지는 Static으로 만들고 클라이언트에서 인증 이후에 추천 캐릭터를 다시 가져오는 방식을 쓰게 되면, 처음에는 Static으로 생성된 캐릭터가 보였다가 다시 Skeleton이 나타나고 추천 캐릭터가 바뀌는 깜빡임(Flickering) 현상이 발생하게 된다.

초기 경험을 개선하려고 시작한 작업이 오히려 UX를 더 불안정하게 만드는 셈이었다.


3. 회원은 SSR, 비회원은 SSG 으로 렌더링

그래서 최종적으로 선택한 방식은 메인 페이지를 두 개의 경로로 나누는 것이었다.

  • / : 비회원용 메인 페이지
  • /main : 인증된 유저용 메인 페이지

이 라우팅은 미들웨어에서 처리했다.
유저가 /로 접근했을 때 이미 인증된 상태라면 화면이 보이기 전에 /main으로 포워딩하였다.

이렇게 하면 인증된 유저는 /main에서 서버 사이드 렌더링, 비회원 유저는 /에서 Static Generation을 각각 적용할 수 있었다.

즉, 비회원은 항상 동일한 추천 캐릭터가 보이면 되므로 Static Page를 보여주고, 회원은 개인화된 추천 캐릭터가 보이면 되므로 SSR Page로 하면 초기 로딩 속도와 개인화 UX를 모두 만족시키는 구조가 만들어졌다.


4. 비회원은 Incremental Static Regeneration (ISR) 으로 전환

하지만 여기서도 한 가지 문제가 남아 있었다.
추천 캐릭터는 주기적으로 바뀌어야 한다는 점이다.

Static Generation으로 만든 페이지는 빌드 시점의 데이터만 보여주기 때문에,
시간이 지나도 추천 캐릭터가 바뀌지 않는다.
그래서 / 경로에는 ISR(Incremental Static Regeneration) 을 적용했다.

기본적으로는 Static Page로 빠르게 제공하고 일정 주기마다 페이지를 다시 생성해서 비회원 유저에게도 주기적으로 바뀌는 추천 캐릭터를 보여주도록 했다.


결과

3초에서 0.04초로 98% 정도 개선시킬 수 있었다.
주소창을 보면 알겠지만 로컬 환경이 아니라 Production 환경이다.

물론 Amplify의 Cold Start 등 여러 요소가 초기 로딩 시간을 좌우하긴 하지만, 엄청난 성능 개선이 있었다는 점은 분명하다.

브라우저에서는 테스트를 위해 일부러 CPU와 Network에 Throttling을 걸어서 여러 환경을 테스트 할 수 있다.

CPU는 4배 느리게, 네트워크는 느린 4G로 상당히 안 좋은 환경에서 테스트해도 Production 서버 기준 1.77 초가 나온 것을 알 수 있다.

  • 이 환경에서 이전 React에서는 10-13초 정도 걸렸다…

모든 페이지에 하나의 렌더링 전략이 정답일 수는 없다.
유저의 상태와 페이지의 역할에 맞게 가장 적절한 렌더링 방식을 선택하는 것이 성능 최적화의 핵심이었다.

초기 로딩 시간은 눈에 띄게 줄었고, 서비스는 훨씬 더 완성된 제품처럼 느껴지기 시작했다.
첫 화면이 빠르게 뜬다는 건 그 자체로 서비스에 대한 신뢰를 만든다는 사실을 이번 최적화를 통해 확실히 체감할 수 있었다.

그리고 성능 최적화는 기술 스택만의 문제가 아니라 의사결정의 문제라는 점이다.

어떤 기술을 쓰느냐보다, 언제 어떤 선택을 하느냐가 훨씬 중요하다. 페이지의 특성과 유저의 맥락을 기준으로 한 이 선택들이 쌓이면서, 서비스는 눈에 띄게 단단해졌고, 나 역시 개발자로서 한 단계 더 성장했다는 감각을 얻을 수 있었다.

특히 성능 최적화를 좋아하는 백엔드 개발자로서, 페이지의 성격과 유저의 상황을 기준으로 렌더링 방식을 선택하는 과정과 어떤 상황에서도 준수한 결과를 내도록 최적화한 것이 꽤나 개발자로서 성취감을 느낄 수 있는 프로젝트였다.

댓글 남기기

Dalmeng's Footprints에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기