뒤로 가기개발 이야기
2025-12-06
6분

FCP 4.5초에서 2초대로: 홈페이지 성능 개선

Vercel Analytics에서 FCP 4.5초(Poor)를 확인하고, lazy loading과 번들 최적화를 통해 2~3초대로 개선한 과정을 정리했습니다.

#성능 최적화#FCP#React#번들 최적화
FCP 4.5초에서 2초대로: 홈페이지 성능 개선

어느 날 Vercel Analytics를 보다가,
홈페이지 FCP(First Contentful Paint)가 4.5초라는 걸 확인했습니다.

딱 "Poor" 구간.
“아, 이건 그냥 느린 게 아니라 진짜 느린 거구나…” 하는 걸 인정해야 했습니다.

그래서 실제로 내가 무엇을 손봤는지 한 번 남겨두려고 이 글을 쓰게 됐습니다.


문제 인식: FCP 4.5초는 체감상 어느 정도인가

FCP 4.5초라는 건, 유저 입장에서 보면:

링크를 누르고 첫 글자 하나 보는데 4~5초가 걸린다는 뜻입니다.

보통 FCP까지는 이렇게 흘러갑니다:

네트워크 → 서버 응답(TTFB) → HTML 파싱 → JS 다운로드/파싱 → React 렌더 → 첫 픽셀(FCP)

제가 쓰고 있는 스택은 React Router 7 + Vite + Vercel인데, 여기서 느려질 수 있는 지점을 정리해 보니:

  1. 초기 자바스크립트 번들이 너무 무거웠음

    • 홈에 LightRays, AnimatedGridPattern, NumberTicker 같은 화려한 UI와 애니메이션 욕심
    • 에디터(TipTap), 차트, 대시보드 관련 컴포넌트가 레이아웃 쪽에서 같이 딸려오는 구조
  2. 서버 위치 / TTFB 이슈

    • Vercel 기본 리전이 미국 쪽이면, 한국에서 접속할 때 왕복 지연만으로도 200~300ms는 그냥 쓰고 들어감
  3. 처음 화면에 안 보여도 되는 것들을 다 같이 렌더

    • 페이지 아래 카드 리스트, 이미지, 애니메이션까지 한 번에 그리려다 보니 첫 픽셀 나오기까지가 전체적으로 밀림

문제는 대충 이렇게 보였습니다.


내가 선택한 해결 방향: 세 가지 축

이번에 잡은 방향은 크게 세 가지였습니다.

  1. 홈 화면 번들 다이어트
  2. 네비게이션 / 인증 레이아웃 정리
  3. 페이지 아래 섹션 지연 로드

하나씩 정리해볼게요.


1. 홈 화면 번들 다이어트

목표는 단순했습니다.

홈페이지에 들어올 때, 정말 필요한 것만 같이 들어오게 만들자.

그래서 일단 기준을 이렇게 잡았습니다:

// ❌ 이렇게 하면 안 됨 (홈 들어갈 때 같이 번들됨)
import { DashboardScreen } from "~/features/dashboard/screens/dashboard-screen";

// ✅ 대시보드 라우트에서만 필요할 때 불러오기
const DashboardScreen = lazy(() =>
  import("~/features/dashboard/screens/dashboard-screen")
);

실제로 바꾼 것들

  • 페이지 아래에 있는 섹션들 전부 lazy load로 변경

    • StorySection
    • InsightsSection
    • PersonaSection
    • FAQPreview
    • FinalCTASection
  • Hero 섹션 안에서도 상대적으로 무거운 애니메이션 컴포넌트 분리

    • LightRays
    • AnimatedGridPattern
    • NumberTicker

이렇게 분리해놓으니까, 초기 번들 크기가 대략 30~40% 정도 줄어드는 효과가 있었습니다. (정확한 수치는 빌드 리포트 기준이지만, 체감도 분명히 달랐습니다.)


2. Navigation / Layout에서 인증 처리 정리

다음으로 건드린 부분은 네비게이션 레이아웃입니다.

문제 상황

홈 페이지에서도 굳이 필요 없는데, 처음부터 로그인 관련 데이터베이스 클라이언트를 초기화하고 사용자 정보를 가져오려는 시도를 하고 있었습니다.

“헤더에 로그인 상태를 보여주고 싶으니까…” 하다가, 결국 FCP 타이밍에까지 영향을 주고 있었던 셈입니다.

처음 시도했던 방법

처음에는 아예:

“홈에서는 로그인 관련 호출을 싹 다 빼버릴까?”

라고 생각하고 실제로 그렇게 한 번 바꿔봤는데, 그러면 또 로그인 했는지 안 했는지 알 수 없어서 사용자 경험이 미묘해집니다. (내가 로그인했는데도 ‘로그인’ 버튼만 떠 있으면 은근히 거슬리니까요.)

최종적으로 선택한 방법

결국 인증 자체를 없애기보다는, 에러 흐름을 최대한 빠르게 끊는 쪽으로 정리했습니다.

// 인증되지 않은 경우 빠르게 반환
if (authError || !authData?.user?.id) {
  return { user: null };
}

getUser()는 쿠키 기반이라 응답이 굉장히 빠른 편이고, 에러가 나는 경우에도 위처럼 즉시 반환하게 만들어두면:

  • FCP에는 거의 영향을 주지 않으면서
  • 로그인 상태는 자연스럽게 유지

하는 쪽으로 균형을 맞출 수 있었습니다.


3. 페이지 아래 섹션 지연 로드

마지막으로 한 번 더 크게 칼을 댄 부분은 페이지 아래 콘텐츠 처리였습니다.

“처음 들어왔을 때 당장 안 보이는 것들은 그냥 나중에 로드해도 되지 않나?” 이 생각으로, 페이지 아래 섹션을 전부 Suspense로 감쌌습니다.

<Suspense fallback={<div className="h-64" />}>
  <StorySection stories={stories} />
</Suspense>

이렇게 해두면:

  • 첫 화면(페이지 위)은 가볍게 먼저 그려주고
  • 유저가 스크롤을 내리기 시작할 때쯤, 뒤쪽 콘텐츠가 자연스럽게 따라오는 흐름

을 만들 수 있습니다.


추가로 건드린 부분들

SpeedInsights도 lazy load로 분리

성능 모니터링 도구도 그냥 초기 렌더에 같이 태워버리면 손해라서, 이것도 뒤로 뺐습니다.

const SpeedInsights = lazy(() =>
  import("@vercel/speed-insights/react").then((m) => ({
    default: m.SpeedInsights,
  }))
);

성능을 확인하기 위한 도구 때문에 성능을 깎아먹는 상황은 만들지 말자는 느낌으로 정리했습니다.


적용 후 변화

수치적인 변화 (대략)

  • FCP: 4.5초 → 2~3초대로 개선(브라우저, 네트워크 상황 따라 편차 존재)

  • 초기 번들 크기: 약 30~40% 감소

  • TTFB 영향 요소:

    • 홈에서 불필요한 로그인 관련 호출을 최대한 얇게 만들면서, 체감 지연도 같이 줄어듦

체감적인 변화

제가 직접 써보면서 느낀 건:

  • 첫 화면이 “텅” 하고 늦게 뜨던 느낌이 줄어들고
  • 바로 Hero 섹션이 보이는 느낌이 훨씬 강해졌고
  • 스크롤을 내릴 때마다 밑에 내용들이 자연스럽게 따라오는 흐름이 만들어졌다는 것

로그인 상태도 같이 잘 표시되기 때문에, 처음에 고민했던 “성능 vs 기능” 갈등도 어느 정도 정리가 됐습니다.


이번 작업에서 정리해 둔 것들

1. Lazy Loading은 이제 선택이 아니라 기본값에 가깝다

React Router 7 + Vite 환경에서는 라우트 단위 코드 스플리팅이 어느 정도 기본으로 되지만, 페이지 안에서도 컴포넌트 단위로 나눠서 lazy loading을 해주면 체감이 확실히 달라집니다.

특히 “지금 당장 안 보여도 되는 것들”은 의식적으로 뒤로 미루는 습관을 가져야겠다는 생각이 들었습니다.

2. 페이지 위 / 페이지 아래를 구분해서 설계하기

이제는 화면을 설계할 때 자연스럽게:

  • 페이지 위: 진입 시 꼭 보여야 하는 것들만 배치
  • 페이지 아래: 스크롤 이후에 로드해도 되는 것들

이렇게 나눠서 생각하게 됐습니다.

디자인이 예쁘냐보다, “첫 화면이 빨리 뜨냐”가 훨씬 중요한 구간이라는 걸 몸으로 다시 한 번 느꼈습니다.

3. 성능과 기능 사이에서 타협하는 법

처음에는 “그냥 홈에서는 인증 호출을 싹 끊어버릴까?” 했다가, 직접 써보니 이게 또 너무 삭막해서 마음에 안 들었습니다.

결국:

  • 인증 자체는 유지하되
  • 에러나 비로그인 상황에서 빠르게 빠져나오는 흐름을 만들고
  • 성능과 기능을 동시에 챙기는 방향

으로 정리했습니다.

이번 작업은 거창한 튜닝이라기보다, “내가 실제로 느리다고 느낀 부분을 어떻게 줄여 나갔는지” 를 기록해 둔 정도에 가깝습니다.

그래도 비슷한 스택을 쓰는 분들이라면, 초기 번들 다이어트 + 페이지 아래 lazy load + 인증 흐름 정리 정도만으로도 생각보다 꽤 체감 차이를 만들 수 있을 거라고 봅니다.

이재황

이재황

대표 세무사 | 개발자