들어가며
프론트엔드 성능 최적화를 진행하다 보면, 의외의 병목 지점을 발견하게 됩니다. 저희 팀에서는 폴리필(Polyfill)이 메인 스레드를 과도하게 점유하고 있다는 사실을 발견했고, 이를 개선하기 위한 여정을 시작했습니다.
이 글에서는 폴리필 최적화를 시도하며 겪은 문제와 의사결정 과정을 공유합니다.
문제 발견: 폴리필이 성능을 잡아먹고 있다
성능 프로파일링 중 폴리필 청크가 비정상적으로 큰 사이즈를 차지하고 있었고, 메인 스레드 점유 시간 역시 상당했습니다. 폴리필은 구형 브라우저 호환을 위해 필수적이지만, 과도한 폴리필은 오히려 대다수 사용자의 경험을 저하시킵니다.
💡 폴리필을 완전히 제거하면? TBT(Total Blocking Time) 100ms 감소, non-gzip 기준 번들 사이즈 약 170KB 절감이 가능했습니다.
수치만 보면 매력적이지만, 하위 호환성을 포기할 수는 없었기에 다른 방법을 모색했습니다.
시도 1: polyfill: 'usage' 옵션으로 전환
현재 프로젝트의 Rsbuild 설정은 polyfill: 'entry'로 되어 있었습니다. 이 옵션은 프로젝트 진입점에 모든 폴리필을 일괄 삽입하는 방식입니다. Git 히스토리를 확인해보니, 최초 프로젝트 셋업 시점부터 이 설정이 유지되고 있었습니다.
// as-is
polyfill: 'entry',
// to-be
polyfill: 'usage',
usage 옵션은 실제 코드에서 사용되는 폴리필만 선별적으로 포함하는 방식으로, 불필요한 폴리필을 제거하여 번들 사이즈를 줄일 수 있습니다.
결과: 빌드 실패 🚨
기대와 달리, usage 옵션으로 전환하자 빌드 시점에 외부 라이브러리들이 깨지는 문제가 발생했습니다. 서드파티 라이브러리들이 특정 폴리필의 존재를 암묵적으로 전제하고 있었기 때문에, 선별적 포함 방식에서는 필요한 폴리필이 누락되어 빌드가 실패한 것입니다.
시도 2: 폴리필 전용 서버 구축 검토
토스 기술블로그의 스마트 폴리필 아티클에서 좋은 접근법을 발견했습니다.
핵심 아이디어는 다음과 같습니다:
- 폴리필 전용 서버(프록시)를 구축하여, 클라이언트의 User-Agent를 기반으로 필요한 폴리필만 동적으로 제공
- Babel 프리셋을 활용하여 빌드 시점의 폴리필은 최소화
이 방식은 구형 브라우저에만 필요한 폴리필을 제공하고, 최신 브라우저에는 불필요한 코드를 전송하지 않아 이상적인 해결책입니다. 다만, 별도의 서버 인프라 구축과 운영 비용이 수반됩니다.
현재 브라우저 지원 범위
저희 프로젝트의 browserslist 설정은 다음과 같습니다:
"browserslist": {
"production": [
"chrome >= 77", // 2019년 9월
"ios_saf >= 14" // 2020년 9월
]
}
Chrome 77과 iOS Safari 14는 이미 대부분의 모던 JavaScript 기능을 지원하지만, 일부 엣지 케이스를 위해 폴리필이 여전히 필요한 상황입니다.
최종 결론: 현상 유지
여러 방안을 검토한 결과, 아래와 같은 이유로 현재 설정을 유지하기로 결정했습니다.
| 방안 | 장점 | 단점 |
|---|---|---|
polyfill: 'off' | TBT 100ms↓, 번들 170KB↓ | 하위 호환성 포기 |
polyfill: 'usage' | 불필요한 폴리필 제거 | 서드파티 라이브러리 빌드 실패 |
| 폴리필 서버 구축 | 최적의 폴리필 전달 | 인프라 구축·운영 비용 |
현상 유지 (entry) | 안정성 보장 | 번들 사이즈 비효율 |
현재로서는 안정성이 우선이라는 판단 하에 기존 설정을 유지하되, 향후 브라우저 지원 범위를 상향 조정하거나, 폴리필 서버 구축의 ROI가 확보되는 시점에 재검토할 계획입니다.
폴리필 최적화는 단순히 “줄이면 좋다”가 아니라, 호환성·안정성·유지보수 비용 사이의 트레이드오프입니다. 당장의 성능 수치에 매몰되기보다, 프로젝트의 맥락에 맞는 현실적인 판단이 중요하다는 것을 다시 한번 느꼈습니다.