[프론트엔드 고도화-3] 상세페이지 번들링 최적화

Administrator||조회수 54


#3 Next.js 번들 분석: react-syntax-highlighter 최적화로 초기 로딩 JS 210kB 절감하기

주요 기능인 다크 모드(Dark Mode) 도입을 성공적으로 마친 후, 프로젝트는 한 단계 더 성숙해졌다. 하지만 기능의 추가는 필연적으로 코드베이스의 복잡성과 용량 증가를 동반한다. 안정적인 서비스를 운영하기 위해서는 주기적인 '기술적 건강검진'이 필수적이다. 이에 따라, 현재 프론트엔드 애플리케이션의 성능 상태를 진단하고 잠재적인 병목 지점을 찾아내기 위해 번들 사이즈 분석을 진행하기로 결정했다.

분석 도구로는 Next.js 생태계에서 표준적으로 사용되는 @next/bundle-analyzer를 선택했다.

1. 문제 식별: 예상치 못했던 거대한 페이지

분석 도구를 devDependencies로 설치하고, package.jsonnext.config.js에 분석을 위한 설정을 추가했다. ANALYZE=true 환경 변수를 통해 조건부로 실행되도록 구성하여, 기존 개발 및 프로덕션 빌드에는 아무런 영향을 미치지 않도록 방어적으로 신중히 접근했다.

분석 스크립트 실행 후, 터미널에 출력된 빌드 결과는 현재 프로젝트의 전반적인 상태를 명확하게 보여주었다.

# 분석 실행 명령어 pnpm --filter frontend run analyze

빌드 결과 요약:

alt text

대부분의 페이지는 모든 페이지가 공유하는 JS(101kB)를 포함해도 초기 로딩 JS(First Load JS)가 110-120kB 수준으로 매우 양호했다. Next.js의 코드 스플리팅이 효과적으로 동작하고 있다는 증거였다.

💡 코드 스플리팅이란?

Next.js의 코드 스플리팅은 페이지나 기능별로 필요한 JavaScript만 로드되도록 해 초기 로딩 속도를 개선하고 성능을 최적화한다. 기본적으로 페이지 단위로 자동 분리되며, 다이나믹 임포트를 통해 컴포넌트 로딩 시점을 제어할 수 있어 사용자 경험을 향상시킨다.

하지만 단 한 곳, 게시물 상세 페이지(/[postId])가 유독 눈에 띄었다. First Load JS504kB에 달했다. 이는 다른 페이지들의 약 5배에 달하는 크기로, 명백한 최적화 대상이었다.

2. 원인 분석: react-syntax-highlighter의 전체 패키지

터미널 결과와 함께 생성된 시각화 리포트(client.html)는 이 문제의 근본 원인을 정확히 지목했다. 번들에서 가장 큰 비중을 차지하는 것은 코드 블록의 문법 하이라이팅을 위한 react-syntax-highlighter 라이브러리였다.

문제는 MarkdownViewer.tsx 컴포넌트의 import 방식에 있었다.

// 기존 코드 import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';

Prism 컴포넌트는 react-syntax-highlighter가 지원하는 모든 프로그래밍 언어의 파싱 로직을 포함하는 '전체 패키지'이다. 내 블로그에서는 JavaScript, TypeScript, CSS 등 소수의 언어만 사용함에도 불구하고, Java, C++, Swift, Go, Rust 등 사용하지 않는 수십 개의 언어에 대한 코드가 불필요하게 번들에 포함되고 있었다. 이것이 바로 504kB라는 거대한 번들의 주범이었다.

3. 해결 전략: PrismLight를 이용한 화이트리스트 기반 로딩

react-syntax-highlighter는 이러한 문제를 해결하기 위한 PrismLight라는 경량화된 컴포넌트를 제공한다. 이는 필요한 언어만 선택적으로 등록하여 번들에 포함시키는 '화이트리스트' 방식이다.

이 전략은 다음과 같은 장점을 가진다.

  1. 성능: 사용하지 않는 언어 코드를 번들에서 완전히 제외하여 초기 로딩 성능을 극대화한다.
  2. 방어적 접근: 기존 코드의 시각적 결과물(코드 하이라이팅 스타일)을 100% 동일하게 유지한다.
  3. 확장성: 향후 새로운 언어에 대한 포스팅이 필요할 경우, 간단하게 import 구문과 등록 코드 한 줄만 추가하면 유연하게 대응할 수 있다.

다음과 같이 MarkdownViewer.tsx 코드를 수정했다.

// 수정 후 코드 // 1. Prism 대신 PrismLight를 import import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'; // 2. 필요한 언어 파서만 개별적으로 import import ts from 'react-syntax-highlighter/dist/esm/languages/prism/typescript'; import tsx from 'react-syntax-highlighter/dist/esm/languages/prism/tsx'; ...(중간 생략)... highlighter/dist/esm/languages/prism/markup'; // HTML import bash from 'react-syntax-highlighter/dist/esm/languages/prism/bash'; import python from 'react-syntax-highlighter/dist/esm/languages/prism/python'; import json from 'react-syntax-highlighter/dist/esm/languages/prism/json'; // 3. import한 언어들을 SyntaxHighlighter에 등록 SyntaxHighlighter.registerLanguage('ts', ts); ...(중간 생략)... SyntaxHighlighter.registerLanguage('python', python); SyntaxHighlighter.registerLanguage('json', json);

4. 결과 검증: 210kB 절감, 그리고 그 이상의 의미

코드 수정 후, 다시 분석 스크립트를 실행했다. 결과는 기대 이상이었다.

개선된 빌드 결과:

alt text

게시물 상세 페이지의 First Load JS504kB에서 294kB로, 210kB(약 42%) 만큼 극적으로 감소했다.

210kB라는 숫자가 실제 사용자에게 어떤 의미를 갖는지 다양한 네트워크 환경을 기준으로 비교하면 그 효과는 더욱 명확해진다.

네트워크 환경속도 (Mbps)최적화 전 (504kB)최적화 후 (294kB)단축 시간
느린 4G/LTE1.5~2.7초~1.5초~1.2초
100 Mbps100~0.04초~0.02초~0.02초
1 Gbps1,000~0.004초~0.002초~0.002초

개발 환경인 초고속 인터넷에서는 0.02초의 차이가 미미해 보일 수 있다. 하지만 이 최적화의 진정한 가치는 모든 사용자를 고려하는 데 있다. 지하철이나 Wi-Fi 신호가 약한 곳에서 접속하는 사용자의 로딩 시간을 1.2초나 단축시킨 것은 의미 있는 개선이었다.

또한, 다운로드 시간 단축 외에도 브라우저가 파싱하고 실행해야 할 JavaScript 코드의 양 자체가 줄어들었기 때문에, 저사양 모바일 기기에서의 렌더링 성능과 반응성 또한 향상되는 부수적인 이득을 얻었다.

5. 결론

이번 번들 분석과 최적화 과정은 기능 개발만큼이나 성능 점검이 중요함을 다시 한번 일깨워주었다. 특히 서드파티 라이브러리를 사용할 때는, 해당 라이브러리가 제공하는 최적화 방안을 충분히 검토하고 적용하는 것이 필수적임을 깨달았다.

@next/bundle-analyzer를 통한 진단, 그리고 PrismLight를 이용한 선택적 로딩이라는 처방은, 시각적 결과물을 전혀 훼손하지 않으면서도 프로젝트의 성능을 한 단계 끌어올리는 성공적인 경험이었다. 앞으로도 주기적인 성능 측정을 통해, Deep Dive! 블로그를 방문하는 모든 사용자에게 쾌적한 경험을 제공할 수 있도록 노력해야겠다.

Administrator
Written by

Administrator

안녕하세요! Deep Dive! 블로그 제작자 입니다.

댓글을 불러오는 중...