더핑크퐁컴퍼니 웹개발팀이 Next.js 9 에 멈춰 있던 사내 운영 도구를 12 로 올리고 번들을 줄인 기록이다. bundle-analyzer 로 현황을 눈에 보이게 만든 뒤 React 버전업, Babel→SWC 전환, `modularizeImports`, Moment.js 교체를 차례로 적용해 번들을 2.6MB 에서 2.01MB 로 줄였다.
핵심 포인트- 착수 계기는 20분이면 끝나는 요구사항을 처리하러 프로젝트를 열었다가 Next.js 9, deprecated 패키지 경고, 과거 자기 코드를 마주친 것이었다.
- 외부 사용자가 아닌 사내 운영자용 서비스라 이슈가 생겨도 바로 공유할 수 있어 비교적 부담 없이 진행했다.
- Next.js 는 12 로 올렸고 React 는 18 이 아직 완전히 지원되지 않는 라이브러리를 고려해 17 로만 올렸다. 새 JSX transform 덕에 `import React from 'react'` 를 전부 제거했다.
- Babel 을 걷어내고 Next 12 의 Rust 기반 SWC 를 쓰자 로컬 빌드가 40.40초에서 21.00초로 약 2배 빨라졌다.
- Moment.js 는 지원 중단과 트리셰이킹 미지원 때문에 Day.js 로 바꿨다. date-fns 가 더 빠른 경우가 많았지만 마이그레이션 안전성을 우선했다.
상세 정리- 레거시에 대한 태도: 지금 레거시가 된 코드도 작성 당시에는 나름의 최선이었다고 짚으며, 당장 버그를 일으키지 않으면 지나치게 되는 관성을 인정한다.
- 측정 먼저: `@next/bundle-analyzer` 를 도입해 최종 번들 크기를 시각화하고 어디를 줄일지 정했다. 전체가 아주 큰 규모는 아니지만 줄일 여지가 보였다.
- SWC 가 빠른 이유: Rust 가 병렬 처리를 고려해 설계된 언어라 여러 파일을 동시에 처리한다. 이벤트 루프 기반 싱글 스레드인 JavaScript 와 달라 코어가 많고 프로젝트가 클수록 차이가 커진다.
- 공식 수치도 인용한다. 로컬 새로고침 3배, 프로덕션 빌드 5배, 컴파일 자체는 Babel 대비 17배다.
- `modularizeImports`: `import { merge } from 'lodash'` 는 사실상 lodash 전체를 불러오는 것과 같다. 그래서 개별 경로로 import 하거나 lodash-es 를 쓰라는 조언이 많았는데, Next 12 의 이 옵션을 쓰면 빌드 타임에 알아서 변환된다.
- 날짜 라이브러리 비교: npm trends 다운로드 수는 date-fns 가 높고, unpacked size 는 Day.js 가 훨씬 작다. Day.js 가 작은 것은 플러그인을 뺀 코어 기능만 갖기 때문이다.
- date-fns 의 강점은 트리셰이킹 지원과 여러 API 의 성능 우위였지만, 최종 선택은 Day.js 였다.
- Day.js 를 고른 세 가지 이유: Moment.js 와 같은 API 를 제공해 마이그레이션이 안전하고 쉽다, locale 을 포함하면 date-fns 도 결코 작지 않다, 코어 기능만 있어 불필요한 플러그인을 끌고 오지 않는다.
- 주의점: Day.js 는 Moment.js 와 달리 immutable 이라 `add(1, 'day')` 의 결과가 원본에 반영되지 않는다. 마이그레이션 시 이 차이를 놓치면 안 된다.
- 판단 기준을 한 줄로 남긴다. 전체 성능 차이가 미미하다면 더 빠르게 작업할 수 있는 쪽을 고르는 게 낫다는 것이다.
- 회고 ①: 역할을 담아내지 못한 변수명 때문에 코드 내부 로직을 다시 파악해야 했고, 여러 기능이 한 함수에 몰려 있으면 더 어려웠다.
- 회고 ②: 전체는 아니어도 복잡한 로직과 유틸 함수에 테스트가 있었던 덕에 마음 편히 고칠 수 있었고, 리팩터링 중 놓친 실수를 실제로 잡아 줬다.
왜 읽나버전이 한참 밀린 Next.js 프로젝트를 손봐야 하는 프론트엔드 개발자에게 어떤 순서로 무엇부터 바꿀지와 실측 개선치를 준다.