다이어트는 다음이 아니라 지금 해야한다.

번들 사이즈 최적화 기법 - Next.js

12 min read
Created

2025.07.05.

Last updated

2025.07.05.

빠른 참고

Next App 번들링 최적화 기법

점점 커져가는 너

Next.js가 App Router를 통해 서버 컴포넌트를 도입한 시점 이후로 프레임워크 자체의 번들 사이즈가 많이 커졌다.

버전 14때만 해도 봐줄만 했는데, 15에서 약 20% 정도 또 커졌다.

SPA 잘 쓰다가 Next.js로 전환하면서 신세계를 느꼈었는데, 요즘 우후죽순으로 나오는 FE 프레임워크들을 찍어 먹다가도

Next.js 만한게 없지 하면서 또 돌아오게 된다. 역시 커뮤니티가 최고다

그리고 React 채신 기술(서버 컴포넌트!)들을 잘 활용하려면 대체제가 없기도 하다...

사실 나는 Next.js를 많이 좋아한다. 그저 이 녀석을 영혼까지 활용하고 싶을 뿐이다.

서버 컴포넌트 베이스

React v18 부터 서버 컴포넌트 기법이 도입되었다.

하지만 번들러가 서버 컴포넌트를 특수하게 처리해줘야 가능하기 때문에, 그냥 사용할 순 없다.

그래서 현 시점 Next.js 에서도 App Router를 사용해야 한다.

프로젝트를 빌드할 때, 번들 사이즈를 우선적으로 고려해야한다. 마법 같은 최적화들은 알아서 해주기 때문에 굳이 신경쓰지 않아도 된다.

서버 컴포넌트를 사용하면 빌드할 때, 제로 번들 사이즈를 실현할 수 있다. 즉, 해당 컴포넌트에서 사용한 외부 패키지가 클라이언트로 전송되지 않는다.

따라서 컴포넌트를 구현할 때, 서버 컴포넌트를 기반으로 구현하면 번들 사이즈가 확실하게 많이 줄어드는 것이 체감된다.

그런데 여기서 기능적 컴포넌트를 서버 컴포넌트로 구현하다가, 클라이언트 코드가 필요하면 클라이언트 컴포넌트를 따로 또 생성해야하는 약간의 아이러니함이 발생하는데,

이 문제는 컴포넌트를 어떻게 그룹지을까? 라는 의문을 남긴다... 또 다시 디렉토리 구조 바꿔야한다

컴포넌트 짝짓기

지극히 개인적인 스타일이다.

컴포넌트를 구현하다 보면 클라이언트와 상호작용해야할 일이 반드시 생긴다.

이럴 때는 클라이언트 컴포넌트를 구현해줘야 하는 데, 귀찮다고 그냥 'use client' 지시어를 넣고 개발하는 순간 편리함을 얻고 성능은 잃어버리게 된다.(과학자라면 신경 안써도 된다)

이왕 App Router를 사용하기로 맘먹었다면 서버 컴포넌트를 극한으로 활용해야한다.

결국엔 서버 컴포넌트 기반으로 설계하고, 필요할 때마다 클라이언트 컴포넌트를 추가해야하는데, 어떻게 하면 현명하게 그룹지을 수 있을까를 지금 이 순간에도 고민하고 있다.

지금 나의 결론은 다음과 같다.

  • app/ 디렉토리에는 페이지 라우트에 대한 '서버 컴포넌트'만을 구현한다. (다른 공통 컴포넌트는 여기에 생성하지 않는다.)
  • components/section 디렉토리에 각 페이지에 대한 섹션 별 '클라이언트 컴포넌트'를 구현한다.
  • 공통 컴포넌트는 components 디렉토리 하위에 배치하는데, 서버 컴포넌트와 클라이언트 컴포넌트를 기능에 맞게 구현한다.

app/ 디렉토리 활용법

진짜 치열하게 토론할 거리를 찾는다면 이 주제가 적합할 것 같다. 공식 문서에도 안에 써도 되고 밖에 써도 된다고 하니깐 더 그런 것 같다.

하지만 난 app/ 디렉토리는 프레임워크 목적에 맞는 컴포넌트(라우팅, 레이아웃 등등)만 넣어야 한다고 생각한다.

app/ 디렉토리 안에다가 넣는 것이 좋다고 하는 사람들은, 어차피 Next.js가 그런거 다 무시하고 오히려 페이지 내부에서 사용하는 공통 컴포넌트를 그룹화 할 수 있어서 시인성 측면에서 유리하다는 의견을 제시한다.

그것도 맞는 것 같다. 하지만 내 스타일은 아니다(단호).

뭐... 각각의 장점들이 있지만, 특정 페이지에서 사용하는 섹션 목적의 컴포넌트를 제외하면, 여러 페이지에서 공통적으로 사용할 컴포넌트를 구현할 수밖에 없으니 app/ 디렉토리 외부나 바로 밑에 따로 모아두는게 맞는 것 같기도 하다.

핵심은 app/ 디랙토리 내부에는 최대한 서버 컴포넌트만 구현하는 것이다.

왜냐면 'use client' 지시어를 사용한 클라이언트 컴포넌트에서 서버 컴포넌트를 불러올 수 없기 때문이다.

이럴 때는 페이지 전체를 클라이언트 컴포넌트로 만드는 방법 보다, 필요한 부분만 클라이언트 컴포넌트를 사용하는 것이 성능 측면에서 유리하다.

섹션 컴포넌트

어차피 한 페이지 안에서만 사용하는 컴포넌트라 페이지 라우팅에 같이 그룹하면 훨씬 깔끔할 수도 있다.

하지만 내 스타일은 프레임워크의 파일 시스템 규칙에 맞게 사용하는 것이라, 규칙에 맞지 않는 파일은 용납할 수 없다.

그래서 나는 components/ 디렉토리 내부에 section/ 또는 page/ 디렉토리로 섹션 컴포넌트를 구분짓고 있다.

여기서도 서버 컴포넌트로 구현 가능한 요소는 최대한 페이지 라우트 컴포넌트에서 구현하고, 클라이언트 상호작용이 필요한 섹션들을 주로 넣는다.

물론 깔끔함을 위해 서버/클라이언트 상관없이 섹션 컴포넌트를 모두 그룹짓고 파이지 라우트 컴포넌트에서 불러다가 사용하는 식으로도 요즘 많이 활용한다.(가독성이 더 좋은 느낌?)

프로젝트 규모에 따라서 적절하게 리팩토링 하면 될 것같다. 아니면 기분따라???

공통 컴포넌트

프로젝트 규모가 커지면, 여러 페이지에서 공통적으로 사용하는 컴포넌트 패턴이 필연적으로 나오는데, 이를 공통 컴포넌트로 그룹지어 관리하면 편하다.

근데 여기에 서버 컴포넌트와 클라이언트 컴포넌트를 같이 구현할 수 있는데, 목적에 맞는 컴포넌트를 설계하는 것이 많이 중요하다.

특히 클라이언트 컴포넌트를 설계할 때, 항상 렌더링할 요소를 children으로 관리하면, 서버 컴포넌트를 최대한 활용 하면서, 클라이언트와 상호작용 할 수 있는 컴포넌트를 생성할 수 있다.

지연 로딩

지연 로딩도 중요하다.

Next.js에서 컴포넌트를 지연 로딩 하고싶다면 next/dynamic을 활용하면 된다.

팝업 같이 클라이언트 상호작용에 의해서만 렌더링 되는 요소들은 지연 로딩 기법을 적극 활용하면, UX에 방해되지 않으면서도 클라이언트로 초기 전송되는 코드를 많이 줄일 수 있다.

또한 애니메이션과 같이 기본적으로 번들 사이즈가 큰 서드파티 라이브러리들이 있는데, 해당 라이브러리들은 기본적으로 지연 로딩하는 방법들을 소개해주고 있기 때문에, 반드시 활용해야한다.

이미지

Next.js 프로젝트를 사용하면 next/image 컴포넌트를 이용해서 이미지를 로드하라고 거의 강제하는 수준이다.

<Image /> 컴포넌트, 추가하는 순간 번들 사이즈가 5kb 늘어난다.(현 시점)

<Image /> 컴포넌트는 Vercel로 배포하지 않는다면 반쪽짜리 컴포넌트이기 때문에(물론 인프라를 구축하고 사용하면 된다!), 그냥 img 태그를 이용해서 이미지를 로딩하는 것이 더 현명할 수도 있다.

이미지 소스가 다양하거나 변경이 잦은 이미지만을 프로젝트에서 활용한다면 <Image /> 사용해야한다는 룰은 과감히 꺼버리고 img 태그로 불러와도 좋을 것같다.

근데 Vercel을 사용하면 <Image /> 컴포넌트를 사용하는게 유라히지만 번들 사이즈가 5kb 늘어나는건 각오해야한다.ㅜㅜ(생각보다 크다...)

여담으로 next/image 사용할 때, deviceSizes 설정 꼭 커스터마이즈 해줘야한다. 기본 값으로 구성하면 쓸모 없는 이미지가 생각보다 많이 생성되어. 사용량에 영향을 많이 준다.

서드파티 라이브러리

는 어렵겠지만, ESM을 지원하는 것 위주로만 사용하면 번들 사이즈를 많이 줄일 수 있다.

그렇지 않다면 사용하지 않는 모듈들은 번들에 포함되지 않도록 import에 신경 써야한다.

서버 컴포넌트와의 관계

만약 서드파티 라이브러리를 서버 컴포넌트에만 사용하고 있다는 것이 확실하면 빌드할 때, 번들 자체에 포함되지 않도록 제외할 수 있다.

프레임워크의 serverExternalPackages에 배열로 추가해주면 되는데, 이미 유명한 라이브러리들은 다 포함되어 있지만, 사용하는 패키지의 사이즈가 제법 크고, 여기 목록에 포함이 안되어있다면 이 설정을 통해 다이어트를 확실하게 할 수 있다.

아이콘 같은건

실험적 기능이긴 하지만 experimental.optimizePackageImports를 활용하면 아이콘 같이 수백, 수천 개의 모듈을 내보내는 라이브러리에서, 프로젝트에서 사용하는 모듈만 내보내게끔 해서 번들 사이즈를 많이 줄일 수 있다.

이것도 이미 유명한 패키지는 다 들어 있으니깐 여기 목록에 없다면 구성해보는 것도 좋을 것 같다.

정리

세 줄 요약하면

  • 서버 컴포넌트를 극한까지 활용해야한다.
  • 지연 로딩도 중요하지만 UX를 해치지 않는 선에서 센스있게 처리해야한다.
  • Next.js 구성 설정을 최대한 활용하자.

그나저나 판올림 할 때마다 자꾸 커지는거 이거 어떻게 안될까??