본문 바로가기
프로젝트

포켓몬 프로젝트 최적화 일지 (FCP, Speed Index 절반 감소)

by 아촌 2025. 4. 13.

 

웹 성능 최적화는 사용자 경험을 향상하는 핵심 요소입니다. 최근 저는 Vite로 구축된 포켓몬 프로젝트의 성능을 대폭 개선했습니다.

포켓몬 프로젝트는 https://pokeapi.co/ poke api를 활용한 도감 사이트입니다.

 

첫번째 Lighthouse 측정결과

 

정확한 성능 측정 방법

성능 최적화를 시작하기 전에 정확한 기준점을 설정하는 것이 중요합니다. 개발 환경에서의 측정은 실제 사용자가 경험하는 성능과 차이가 있을 수 있습니다. 따라서 다음과 같은 환경에서 측정했습니다.

  • 개발(dev) 환경이 아닌 프로덕션 빌드 사용
  • 모든 브라우저 확장 프로그램(익스텐션) 비활성화
  • 캐시 및 기타 사이드 이펙트를 방지하기 위해 시크릿 모드에서 웹 페이지 실행
  • Lighthouse를 통한 객관적인 성능 지표 측정

이러한 설정을 통해 외부 요인 없이 순수한 성능 지표를 얻을 수 있었습니다.

 

npm run build
npm run preview

 

1.  폰트 최적화: 사용하지 않는 폰트 두께 제거하기

 

사용하지 않는 두께의 폰트도 모두 받는 모습입니다. 이 프로젝트에서는 실제로 세 가지 폰트 두께만 사용하고 있습니다.

 

  • font-medium (500)
  • font-semibold (600)
  • font-bold (700)

 

 

결과

  • 폰트 로딩 시간: 370ms > 226ms
  • 첫 번째 콘텐츠풀 페인트(FCP) 시간 개선

 

2.  비동기 CSS 로딩 트릭 활용하기

최적화 전, 후

일반적으로 HTML 문서의 <head> 섹션에 있는 CSS 파일은 렌더링 차단 리소스로 취급됩니다. 브라우저는 이 CSS를 다운로드하고 파싱 할 때까지 페이지 렌더링을 진행하지 않습니다. 중요하지 않은 CSS나 "above the fold" 콘텐츠에 필요하지 않은 CSS까지 이렇게 처리된다면, 사용자는 빈 화면을 오래 보게 될 수 있습니다.

 

해결책: media="print" 트릭

이 문제를 해결하기 위한 우아한 방법 중 하나가 media="print" 속성과 onload 이벤트를 조합하는 것입니다:

이 코드를 분석해보겠습니다:

  1. media="print": 브라우저에게 이 스타일시트는 인쇄용이므로 즉시 렌더링에 필요하지 않다고 알립니다. 브라우저는 이 CSS를 렌더링 차단 리소스로 취급하지 않고 백그라운드에서 다운로드합니다.
  2. onload="this.media='all'": CSS 파일이 완전히 로드된 후 media 속성을 all로 변경하여 모든 화면에 적용되도록 합니다.

결과 

  • 데이터 페칭 요청 시작시간 대폭 감소

응답 요청 시간

  • 응답 요청 시간 224.29ms => 101.16ms (50% 이상 감소)

 


폰트 최적화 이후 라이트하우스 아직은 아쉬운 성능 지표

3. prefeching

 

프리페칭은 사용자가 특정 리소스를 요청하기 전에 미리 데이터를 가져와 캐시에 저장하는 기법입니다. 이를 통해 사용자가 실제로 데이터를 요청할 때 네트워크 요청 없이 즉시 데이터를 제공할 수 있어 지연 시간을 크게 줄일 수 있습니다.

 

첫 화면의 포켓몬 8마리에 대해서 프리페칭을 하였습니다.

export const PREFETCH_POKEMONS = [
  "bulbasaur",
  "ivysaur",
  "venusaur",
  "charmander",
  "charmeleon",
  "charizard",
  "squirtle",
  "wartortle",
];

const Root = () => {
  const queryClient = useQueryClient();

  useEffect(() => {

    PREFETCH_POKEMONS.forEach((name) => {
      // 포켓몬 상세 정보 프리페칭
      queryClient.prefetchQuery({
        queryKey: ["pokemonInfo", name],
        queryFn: () => getPokemonInfoWithId(name),
        staleTime: Infinity,
      });

      // 포켓몬 종 정보 프리페칭
      queryClient.prefetchQuery({
        queryKey: ["pokemonSpec", name],
        queryFn: () => getPokemonSpec(name),
        staleTime: Infinity,
      });
    });
  }, []);

  return (
    <>
     ...
    </>
  );
};

export default Root;

 

 

결과

  • 프리페치 이후 퍼포먼스 점수가 대략 7~10점 상승
  • 첫 화면 체감시간이 상당히 줄어들었음

 

4. SEO 점수 올리기

검색엔진 최적화(SEO)는 웹사이트가 검색 결과에서 더 잘 노출되도록 하는 중요한 요소입니다. 라이트하우스 도구는 SEO 점수가 낮은 원인과 개선 방법을 명확하게 보여주므로, 이를 따라 하나씩 해결해 나가면 됩니다.

1. robots.txt 파일 추가

2. 메타 태그 추가

 

페이지 별로 "react-helmet-sync" 라이브러리를 활용하여 각 페이지에 관련 메타 태그를 추가했습니다.

 

 

결과

 

 

해결하지 못한 부분 및 향후 과제

성능 최적화 과정에서 일부 제약사항과 추가 개선이 필요한 영역이 있었습니다. 이 프로젝트는 PokeAPI를 활용하여 구축되었기 때문에 몇 가지 제약이 있었습니다. 특히 API에서 제공하는 이미지 파일(JPG) 포맷으로 인해 LCP 최적화에 한계가 있었습니다. 외부 API에 의존할 때의 전형적인 도전 과제로, 이미지 최적화나 포맷 변경이 직접적으로 가능하지 않았습니다. 대용량 포켓몬 목록을 효율적으로 렌더링 하기 위해 윈도 가상화 기법을 적용했습니다. 이 방식은 화면에 보이는 항목만 렌더링 하여 메모리 사용량을 크게 줄였지만, 몇 가지 최적화 포인트가 남아있습니다.

  • 불필요한 데이터 페칭: 사용자가 빠르게 스크롤할 때 뷰포트를 지나간 포켓몬들에 대해서도 데이터 페칭이 계속 발생합니다. 이는 네트워크 자원을 낭비하고 성능에 영향을 미칩니다.
  • 향후 개선 방향: 현재 연구 중인 해결책은 각 포켓몬 항목 컴포넌트에 클린업 함수를 구현하여, 해당 항목이 뷰포트를 벗어났을 때 진행 중인 데이터 페칭을 취소하는 것입니다. AbortController와 signal을 활용하여 이를 구현할 계획입니다.