-
Notifications
You must be signed in to change notification settings - Fork 0
Posting/260327 #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Posting/260327 #74
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,176 @@ | ||||||
| --- | ||||||
| title: "프론트엔드 번들링과 압축 전송 톺아보기" | ||||||
| excerpt: "Rendered? Gzip? 빌드 결과물이 CSR, SSR, RSC에서 사용자에게 도달하기까지의 흐름" | ||||||
| coverImage: "/assets/blog/posts/devlog/devlog-6/cover.png" | ||||||
| date: "2026-03-27T14:20:00" | ||||||
| ogImage: | ||||||
| url: "/assets/blog/posts/devlog/devlog-6/cover.png" | ||||||
| --- | ||||||
|
|
||||||
| > 이글은 번들 분석 도구를 써보다가 깊게 조사하게된 번들링과 압축 전송에 관한 글입니다. | ||||||
|
|
||||||
| ## 번들 라이브러리를 써보다가 | ||||||
|
|
||||||
| 안녕하세요! 개발자 김영덕입니다. | ||||||
| | ||||||
| 사이드 프로젝트에 미사용 코드,라이브러리를 정리하다가 "번들 크기 전후 한번 비교 해볼까" 싶었습니다. 그래서 rollup-plugin-visualizer를 설치해서 번들을 시각화해봤는데요. 탭이 **Rendered**와 **Gzip** 두 개의 탭으로 나뉘어져 있었어요. 두 탭이 사용되는 시점이 궁금해서 검색해보다가 제대로 딥 다이브 하게된 내용을 소개합니다. | ||||||
|
|
||||||
|  | ||||||
|
|
||||||
| ### Rendered탭의 의미 | ||||||
|
|
||||||
| pnpm run build를 실행하면 vite의 경우 dist 하위 폴더에 JS 파일들이 생성되는데여. 이 JS파일들이 **Rendered**를 지칭합니다. Vite가 소스코드를 트리쉐이킹, 미니파이, 청크분할 등의 최적화를 거쳐서 만든 **번들링 결과물**의 크기를 의미합니다. | ||||||
|
|
||||||
| ### Gzip탭의 의미 | ||||||
|
|
||||||
| Rendered상태의 파일을 웹서버(Nginx) 또는 CDN(CloudFront, Vercel Edge)이 **Gzip 압축해서 전송**할 때의 크기를 말합니다. 이게 **Gzip** 크기입니다. (참고 : 요즘은 gzip말고도 brotli등 다양한 압축방식들이 있습니다.) | ||||||
|
|
||||||
| ### 간단한 최적화결과 | ||||||
|
|
||||||
|  | ||||||
|
|
||||||
| 코드스플리팅과 라이브러리 청크분리 설정을 통해 새빨갛던 bundle 데이터를 쪼개주었습니다. 적용방법은 매우 간단하여 이를 후술 하지는 않겠습니다. (~~아쉬운건 색깔을 통한 심각도를 정확히 한번에 알수가없다. 보라색은 어느정도란 말인가?~~) | ||||||
|
|
||||||
| ## 궁금했던 점: 로컬 빌드 결과물과 Vercel에 올라간 파일은 다른건가? | ||||||
|
|
||||||
| 번들 분석을 하다보니 이런 궁금증이 생겼습니다. 로컬에서 pnpm run build를 통해 나온 결과물은 Rendered인데, Vercel에 배포된거는 Gzip을 사용한다? 그러면 **로컬에서 빌드한 것과 실제 배포된 것이 다른 용량의 결과물을 사용하는건가?** 생각이 들었습니다. | ||||||
| | ||||||
| 결론부터 말하면, **같은 결과물(Rendered), 같읍 압축(Gzip)을 동일한 플로우로 진행합니다.** 차이가 생기는 건 그냥 실행 환경입니다. 환경에 상관없이 사이트에 사용자가 접속하면 웹서버가 Rendered 파일을 Gzip으로 압축해서 보내주는건데, 이건 파일을 변환하는게 아니라 네트워크 전송 단계에서 압축하는 것입니다. 브라우저가 받으면 자동으로 압축을 풀고, 브라우저는 원래의 Rendered 파일을 실행합니다. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. '같읍'을 '같은'으로 수정하고, '보내주는 건데', '변환하는 게'와 같이 의존 명사 앞의 띄어쓰기를 적용하는 것이 좋습니다.
Suggested change
|
||||||
|
|
||||||
| ## 번들링 구조를 보다보니 — 배포 환경별로 Gzip전송을 어디서 하는지 궁금해졌다. | ||||||
|
|
||||||
| Rendered와 Gzip의 관계를 이해하고 나니, 보통 프론트엔드 배포방식에는 AWS EC2에 직접 서버를 띄우는 방식도 있고, S3 + CloudFront로 서버 없이 서빙하는 방식도 있고, Vercel이나 Netlify 같은 호스팅 플랫폼도 있는데요. 각각 어떤 식으로 Gzip 전송을 처리하는지 궁금해졌습니다. | ||||||
|
|
||||||
| ### EC2 방식: 서버를 직접 띄우는 구조 | ||||||
|
|
||||||
| EC2는 **AWS가 제공하는 가상 컴퓨터 한 대**입니다. 서울 리전에 만들면 서울 데이터센터에 리눅스 서버가 하나 생기는거고, 거기에 직접 Nginx(웹서버)를 설치하고, 빌드결과물을 올려두는 구조입니다. | ||||||
| | ||||||
| Nginx 설정에 gzip on; 이 있으면 전송 시 Gzip 압축을 하여 전송을 해주는 겁니다. | ||||||
| | ||||||
| 이때 EC2는 빈 리눅스 서버기때문에 Nginx같은 웹서버는 따로 설치를 해줘야 합니다. | ||||||
|
|
||||||
| 즉 EC2는 컴퓨터만 빌려주는거고, 그 위에 뭘 설치하고 어떻게 설정할지는 전부 개발자/인프라 담당자 몫입니다. | ||||||
| 온프레미스도 가상화 여부만 다를 뿐, 서버 위에서 하는 작업(Nginx 설치, 설정 등)은 EC2와 동일합니다. | ||||||
|
|
||||||
| ### S3 + CloudFront 방식: 서버 없이 서빙하는 구조 | ||||||
|
|
||||||
| CloudFront는 AWS가 **전 세계에 이미 깔아놓은 엣지 서버 네트워크**입니다. 개발자가 서버를 만드는게 아니라, AWS가 이미 만들어놓은 인프라를 이용하는 겁니다. | ||||||
|
|
||||||
| 이 구조에서는 **서버가 없습니다.** S3는 파일 저장소 역할만 하고, CloudFront가 사용자에게 파일을 전달합니다. | ||||||
|
|
||||||
| ### CloudFront(CDN)는 어떻게 동작하는가? | ||||||
|
|
||||||
| 1. 개발자가 dist/ 파일을 S3에 업로드 | ||||||
| 2. CloudFront에 "이 S3를 써라" 세팅 | ||||||
| 3. 유저가 접속하면: | ||||||
|
|
||||||
| 첫 번째 유저 (캐시 없음) | ||||||
| 유저 → 가까운 엣지 서버 → "캐시 미스" → S3에서 가져옴 → 유저에게 전달 + 가까운 엣지 서버에 데이터 캐싱 | ||||||
|
|
||||||
| 두 번째 유저 (캐시 있음) | ||||||
| 유저 → 가까운 엣지 서버 → "캐시 히트" → 바로 전달 (S3 안 거침) | ||||||
|
|
||||||
| CloudFront 자체는 컴퓨터가 아니라 CDN Content Delivery Network서비스(캐시 + 전달 네트워크)라는 점입니다. 파일을 캐싱해서 빠르게 뿌려주는 역할만 합니다. Gzip/Brotli 압축도 CloudFront에서 해주는데, 기본값은 꺼져있고 압축 옵션을 켜줘야 한다고 합니다. | ||||||
|
|
||||||
| ## 실제로 Gzip 전송을 확인해봐야지 | ||||||
|
|
||||||
| 여기까지 이해하고 나니, 실제 서비스에서 정말 Gzip으로 전송되는지 직접 확인해보고 싶었습니다. 브라우저 개발자도구 Network 탭에서 확인할 수 있습니다. | ||||||
| | ||||||
| 네이버에 들어가 네트워크탭의 API통신 하나를 선택하여 Response Headers를 봤더니 | ||||||
|
|
||||||
|  | ||||||
|
|
||||||
| **Content-Encoding: gzip** 을 확인 할 수 있었습니다. | ||||||
| | ||||||
| 이런식의 gzip을통한 전송이 가능한 이유는 HTTP 프로토콜에 이런 약속이 있기 때문입니다: | ||||||
|
|
||||||
| 브라우저 → 요청 헤더: "Accept-Encoding: gzip" (나 gzip 풀 수 있어) | ||||||
| 서버 → 응답 헤더: "Content-Encoding: gzip" (이거 gzip으로 보낼게) | ||||||
| 브라우저 → 자동으로 압축 해제 후 JS를 파싱해서 실행 | ||||||
|
|
||||||
| ## 궁금한 케이스 총정리 | ||||||
|
|
||||||
| ### 1. 번들링 → 전송 → 렌더링 플로우 | ||||||
|
|
||||||
| React 소스코드 (~500kB라 가정) | ||||||
| ↓ [번들링] Vite가 트리쉐이킹 + 미니파이 + 청크 분할 | ||||||
| Rendered (~120kB) ← 빌드 결과물, dist/assets/\*.js | ||||||
| ↓ 배포 시점에 웹서버/CDN에 세팅해놓음 | ||||||
| ↓ | ||||||
| ↓ [전송] 유저가 요청할 때마다 웹서버(Nginx) 또는 CDN(CloudFront, Vercel)이 gzip/brotli 압축 | ||||||
| Gzip (~30kB) ← 네트워크로 실제 전송되는 크기 | ||||||
| ↓ [렌더링] 브라우저가 gzip 해제 → 다시 120kB → JS 파싱 → React 실행 → DOM 생성 → 화면에 그림 | ||||||
|
|
||||||
| - **번들링**: 소스코드 → Rendered (개발자가 최적화할 수 있는 영역) | ||||||
| - **전송**: Rendered → Gzip (웹서버 또는 CDN이 유저 요청 시마다 담당) | ||||||
| - **렌더링**: Gzip 해제 → 브라우저가 JS를 파싱하고 실행 → DOM 생성 → 화면에 그리는 영역 | ||||||
|
|
||||||
| ### 1-2. 청크 분할 유무에 따른 플로우 | ||||||
|
|
||||||
| 이전의 예시들은 청크 분할을 제외한 예시였는데요 번들링 시 청크 분할(Code Splitting)을 설정해주면 dist하위폴더에 하나의 파일이 아니라 각각의 청크로 쪼갤 수 있습니다. 쪼개진 페이지는 React의 lazy기능을 통해 해당 소스코드에 접근할 때 그 청크만 다운로드하도록 지연 로딩 설정이 가능합니다. | ||||||
|
|
||||||
| [청크 분할 안 했을 때] | ||||||
| 유저가 로그인 페이지 접속 | ||||||
| → index.js (120kB) 전체를 gzip 압축 → ~30kB 전송 → 느림 (모든 페이지 코드 포함) | ||||||
|
|
||||||
| 유저가 대시보드로 이동 | ||||||
| → index.js가 이미 캐싱되어 있으므로 추가 다운로드 없음 → 빠름 | ||||||
|
|
||||||
| 유저가 어드민으로 이동 | ||||||
| → index.js가 이미 캐싱되어 있으므로 추가 다운로드 없음 → 빠름 | ||||||
|
|
||||||
| [청크 분할 했을 때] | ||||||
| 유저가 로그인 페이지 접속 | ||||||
| → index.js (40kB) + login.js (20kB)만 gzip 압축 → ~15kB 전송 → 빠름 (초기 로딩이 가벼움) | ||||||
|
|
||||||
| 유저가 대시보드로 이동 | ||||||
| → dashboard.js (30kB)를 그때서야 gzip 압축 → ~8kB 전송 | ||||||
|
|
||||||
| 유저가 어드민으로 이동 | ||||||
| → admin.js (30kB)를 그때서야 gzip 압축 → ~8kB 전송 | ||||||
|
|
||||||
| 한 번에 gzip 압축해서 보내는 양 자체가 줄어들게 되어 초기 로딩 속도가 크게 빨라집니다. FCP 지표에 영향 | ||||||
|
|
||||||
| ### 2. Next JS같은 서버사이드 렌더링, 리액트 서버 컴포넌트는 Gzip전송을 어떻게? | ||||||
|
|
||||||
| 위의 플로우는 React CSR 기준인데요 Next.js같은 SSR 프레임워크나 리액트 서버 컴포넌트(RSC)는 전송 방식이 어떻게 다를지 궁금해졌씁니다. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
|
|
||||||
| **SSR (Server Side Rendering)** | ||||||
| 유저가 페이지 요청 | ||||||
| → Node.js 서버가 React를 실행해서 **완성된 HTML**을 만듦 | ||||||
| → 이 HTML을 gzip 압축해서 전송 | ||||||
| → 브라우저가 HTML을 받아 바로 화면을 그림 (JS 다운로드 전에도 화면이 보임) | ||||||
| → 이후 JS 번들(Hydration용)을 gzip 압축해서 추가 전송 | ||||||
| → JS가 로드되면 Hydration을 통해 인터랙션이 가능해짐 | ||||||
|
|
||||||
| **RSC (React Server Components)** | ||||||
| RSC는 SSR과 gzip 전송 대상 자체가 다릅니다. SSR은 완성된 HTML을 gzip으로 보내지만, RSC는 **RSC Payload**라는 별도의 직렬화된 데이터를 gzip 압축해서 스트리밍합니다. | ||||||
|
|
||||||
| 유저가 페이지 요청 | ||||||
| → Node.js 서버가 서버 컴포넌트를 실행 | ||||||
| → 실행 결과를 **RSC Payload**(JSON과 유사한 직렬화 포맷)로 만듦 | ||||||
| → 이 RSC Payload를 gzip 압축해서 스트리밍 전송 | ||||||
| → 브라우저의 React가 RSC Payload를 받아 DOM을 구성 | ||||||
| → 클라이언트 컴포넌트의 JS 번들만 별도로 gzip 압축해서 전송 | ||||||
|
|
||||||
| 서버 컴포넌트로 설정된 JS 코드는 클라이언트 번들에 포함되지 않기 때문에, JS로 만들어지는 번들 크기 자체가 줄어드는 효과도 있습니다. | ||||||
|
|
||||||
|  | ||||||
|
|
||||||
|  | ||||||
|
|
||||||
| 실제로 제 블로그의 네트워크 탭을 확인해보면 Content-Type이 text/x-component(RSC Payload 전용 타입)이고, Content-Encoding이 br(Brotli)로 압축 전송되는 것을 확인할 수 있습니다. | ||||||
|
|
||||||
| ### 3. 프로젝트가 작을때는 압축해서 전송하는게 더 느릴 수도 있는거 아닌가? | ||||||
|
|
||||||
| gzip 압축과정을 보면서 이런 생각이 들었습니다. 서울에서 부산까지 휴대폰 하나 배달한다 치면 이삿짐센터에 꽁꽁 싸서 보낼 필요가 있을까? 그냥 바로 출발하는 게 더 빠르지 않을까? | ||||||
|
|
||||||
| 맞습니다. 실제로 아주 작은 파일은 gzip 압축/해제 오버헤드가 전송 시간 절약보다 클 수 있습니다. 일반적으로 **1kB 미만**의 파일은 압축 효과가 거의 없거나 오히려 손해일 수 있습니다. gzip은 압축 메타데이터 자체가 수십 바이트를 차지하기 때문에, 원본이 작으면 압축 후 크기가 비슷하거나 오히려 커지는 경우도 있습니다. | ||||||
|
|
||||||
| 그래서 Nginx 같은 웹서버에서는 gzip_min_length 설정으로 특정 크기 이하의 파일은 압축하지 않도록 설정할 수 있습니다. 역시 압축설정 또한 상황에 맞게 쓰는 것이 중요하다는 내용입니다. | ||||||
|
|
||||||
| ## 마치며 | ||||||
|
|
||||||
| 사실 어느정도의 흐름만 알고 있었는데, AI에이전트시대에 들어가면서 제대로 아는것이 중요하다 생각하게 되어서 이번 기회에 한번 깊게 공부 해보았습니다. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
| | ||||||
| 번들 분석 도구의 탭 두 개에서 시작한 궁금증이 공부하다보니 계속 궁금한게 많아졌는데요, 좋은 프론트엔드 개발자가 되기 위해 앞으로도 딥하게 공부하고 글을 써보겠습니다~ | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'생성되는데여'를 '생성되는데요'로 수정하고, 'Vite'의 대문자 표기 및 띄어쓰기(JS 파일들이)를 적용하여 가독성을 높이는 것이 좋습니다.