#1 Vite란 무엇인가?
1. Vite는 빌드 도구
개발 서버(3000, 5173 등)와 네이티브 ES 모듈을 활용하는 빌드 프로세스를 결합하여, 개발 중 복잡한 번들링의 필요성을 없앤다.
2. 네이티브 ES 모듈이란?
ES6부터 도입된 JavaScript의 공식 모듈 시스템 이다.
이전에는 require()(CommonJS)를 써야 했지만, 이제는 JS 표준에서 import, export 키워드를 사용함.
브라우저가 직접 import 경로를 해석해서 모듈을 불러올 수 있음.
더 이상 번들링 없이도 모듈을 사용할 수 있게 됨.
주요 특징으로는
1) 코드 변경 시 자동 반영(HMR: Hot Module Replacement) 과
2) 코드 분할: 코드를 더 작은 청크로 분할하여 필요한 경우에만 로드
3) 트리 쉐이킹: 사용되지 않는 내보내기가 번들에서 제거되어 애플리케이션의 전체 크기가 줄어듬.
3. Vite의 사용 사례
1) 싱글 페이지 SPA 개발에 적합함.
2) SSG (정적 사이트) 생성 가능: 정적 사이트 생성을 만들고 Vite의 기능을 효과적으로 활용하여 정적 사이트를 생성할 수 있다.
VitePress와 같은 도구로 정적 페이지를 생성할 수 있도록 함.
3) Rollup을 사용한 모듈 번들링: Rollup은 번들링 툴이다. 프로덕션 빌드 단계에서 내부에서 활용한다
3. Esbuild를 사용해서 번들링이 굉장히 빠르다. go 언어로 번들링을 진행함.
4. 처리가 필요 없는 정적 자산 (Json 파일, 원시 텍스트 파일) 등은 public/ 경로에,
움직이게 하는 등 처리를 가하는 asset(자산)은 src/assets에 둔다. (logo.png 등)
webp 이미지를 적극적으로 사용하기.
#2 Vite 시작하기
1. 시작
npm init vite@latest .
npm install
2. 개발 서버 시작
npm run dev
npm run dev에서 기본 프로젝트의 Lighthouse 성능

3, 프로덕션용 빌드
npm run build
4. 빌드 미리보기
npm run preview
또는
npm install -g serve
serve -s dist
로 빌드 후 폴더를 실행해본다.
(빌드 후 Lighthouse)

이후 초기 최적화 과정.
1) 이미지 불러오는 과정 제거
2) index.html meta 태그에 설명 추가
<meta
name="description"
content="기업 분석부터 자기소개서 첨삭, 면접 대비까지! 취업에 필요한 모든 과정 전문가와 함께 준비하세요. 효과적인 전략으로 취업 성공을 앞당깁니다."
/>
3) public/robots.txt 에 코드 추가
User-agent: *
Disallow: /mypage/
Disallow: /private/
Allow: /

(100 100 100 100이 되면 폭죽이 터지는 이스터에그가 있다ㅋㅋ)
이후 어떤 부분 때문에 성능이 떨어지는지 확인할 수 있다
5. 그 외 수행 방법


#3 Vite 에서 다양한 설정
1. 환경 변수
//.env 파일
VITE_API_URL=https://api.example.com
//Vite Project에서
console.log(import.meta.env.VITE_API_URL)
2. 오류 발생시 화면
export default {
server: {
hmr: {
overlay: false
}
}
}
3. 빌드 옵션 - 청킹
export default defineConfig({
build: {
target: "esnext",
assetsDir: "static",
chunkSizeWarningLimit: 600,
},
});
4. 빌드 옵션 Rollup
build: {
rollupOptions: {
external: ['vue'],
output: {
format: 'es',
},
},
}
5. 빌드 옵션 HMR
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
port: 3000,
hmr: true,
strictPort: true,
},
build: {
outDir: "dist",
},
});
6. 나만의 플러그인 만드는 법
function myPlugin(){
return{
name: 'my-plugin',
transform(src, id){
return {
code: src.replace(/console\.log/g, 'console.warn'),
map: null,
}
}
}
}
#4 vite 특징
1. HMR을 사용한 뛰어난 개발 경험.
개발자가 새로고침 없이 코드 수정 결과가 반영되는 기능.
어떻게? 놀랍게도 web socket을 사용한다
1)
"dev": "vite",
설정에 따라
npm run dev
시 vite 실행.
2) Node 프로세스 시작 → Vite CLI 실행
vite.config.js 로딩, 플러그인 초기화
3) HTTP & WebSocket 서버 생성 (같은 포트 사용)
4) 파일 Watcher 등록
5) 브라우저 요청 (localhost:3000 등)
Vite가 HTML, JS, CSS 등 정적 자원을 서빙
HTML 안에 vite/client 스크립트가 자동 삽입
6) 브라우저 연결
vite/client 스크립트가 WebSocket 서버에 연결
파일 변경 시점에 HMR 패킷 수신 대기
7) 파일 변경
Vite Watcher가 감지 → 부분 재빌드 → WebSocket을 통해 수정된 모듈 정보 전송
브라우저 HMR 로직이 받아서 화면에 즉시 반영
2. HMR에서 발생 가능한 문제
1) 상태 관리 문제
일부 복잡한 애플리케이션 상태는 모듈이 교체될 때 예상대로 유지되지 않을 수 있다. 예를 들어, 상태가 교체되는 모듈 내에서 로컬로 관리된다면 이 상태가 손실될 수 있다. 개발자는 애플리케이션 상태가 HMR과 호환 가능한 전역적으로 관리되도록 하거나 상태 관리 라이브러리를 통해 관리되도록 해야 함.
예시)
import React, { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
}
상황 가정
- 개발 모드에서 서버 실행(Vite + React).
- 브라우저 화면에서 현재 카운트를 몇 번 눌러 숫자를 5까지 올렸다고 하자.
- 이제 코드 편집기에서 p 태그 메시지를 수정(예: 현재 카운트: -> Current Count:) → 저장.
- HMR이 동작하여, 이 파일(Counter.tsx)만 부분적으로 교체됩니다.
문제 발생
- 일반적으로 React의 HMR은 컴포넌트가 재실행되면서 로컬 useState 값이 초기 값(0)으로 돌아갈 수 있습니다.
- “HMR 상황에서도 상태가 유지될 때”도 있지만,
- 코드 위치가 변동되거나, 컴포넌트 구조가 달라지거나, 특정 설정이 꼬이면 HMR이 온전히 로컬 상태를 보존하지 못할 수 있습니다.
- 즉, count가 0으로 초기화되면서, 사용자가 보고 있던 5라는 값이 사라집니다(“상태 손실”).
2) CSS 및 스타일 재설정
일부 경우 CSS 또는 스타일 파일을 수정하는 것이 브라우저의 스타일의 계단식 특성으로 인해 예상대로 변경 사항을 적용하지 못할 수 있다. HMR이 일반적으로 CSS를 잘 처리하지만, 의존성이 제대로 추적되지 않으면 개발자는 랜더링 문제를 겪을 수 있다.
#5 브라우저 통신에 대한 이해
localhost:5173라는 URL에 접속했을 때 뜨는 창은 브라우저(클라이언트)에서 표시되는 웹 페이지이고, 그 페이지를 서빙(제공)하는 주체가 로컬 개발 서버(Vite Dev Server)입니다.
- 로컬 개발 서버(Vite Dev Server): Node.js 프로세스로 실행되는 서버가 5173 포트(혹은 설정된 포트)에서 대기 중
- 브라우저: 사용자는 http://localhost:5173에 접속해서 서버가 보내주는 HTML·JS·CSS 등을 렌더링
즉,
- npm run dev → Vite Dev Server가 포트 5173에서 동작 중
- 브라우저에서 localhost:5173을 열면 → Vite Dev Server가 웹 페이지 소스(HTML·JS 등)를 응답
- 브라우저(클라이언트)가 응답 받은 코드를 렌더링 → 화면에 보여줌
따라서 localhost:5173은 개발 서버 주소이고, 거기에 접속했을 때 실제로 우리가 보게 되는 화면은 브라우저(클라이언트)가 그 서버로부터 받아와서 그리는 결과물입니다.
즉!

이 부분은 네트워크 통신 구조와 클라이언트-서버 모델을 이해하면 명확해집니다. 흔히 “주소(URL)는 서버만 가져야 한다”고 생각하기 쉽지만, 실제로는 브라우저(클라이언트) 역시 통신할 때 자신의 IP와 포트를 사용합니다. 다만 우리는 일반적으로 서버 쪽 IP/포트(예: localhost:5173)만 직접 입력하고, 브라우저 쪽 주소는 OS(운영체제)에서 자동으로 할당하기 때문에 눈에 잘 안 보일 뿐입니다.
1. 브라우저(클라이언트)도 통신 시 ‘주소’를 갖는다
- TCP/IP 통신을 할 때는 양쪽(서버와 클라이언트) 각각 **소켓(Socket)**이 필요합니다.
- 소켓은 IP:PORT로 표현되며, 서버가 “수신 대기”하는 포트가 고정되어 있는 것과 달리,
클라이언트(브라우저) 쪽은 **에페메럴 포트(ephemeral port)**라고 불리는 임시 포트를 자동 할당받아 연결을 생성합니다.
즉, 서버가 localhost:5173이면, 브라우저는 예를 들어 localhost:52983 (혹은 127.0.0.1:52983 같은) 임시 포트로 접속을 시도하여 통신 쌍이 형성됩니다.
- 서버: 127.0.0.1:5173 (고정 포트)
- 브라우저: 127.0.0.1:52983 (운영체제가 자동 부여한 임시 포트)
이렇게 되면 TCP 연결이 성립하고, 이후에는 양방향으로 데이터를 주고받습니다.
2. 브라우저가 “도메인”이나 “URL”을 갖지 않는 이유
- 일반적으로는 브라우저 같은 클라이언트는 “접속 대상”을 지정받아 요청을 보내는 쪽이기 때문에, DNS 도메인(예: google.com)이나 명시적인 포트를 표면적으로 갖지 않습니다.
- 서버는 “대기(listen)하는” 쪽이므로, 사람이 식별하기 좋은 형태(도메인, IP, 포트)를 오픈해두죠.
- 반면 클라이언트 주소는 임의의 포트로 할당되었다가, 연결 종료 후에는 사라지기 때문에, 우리가 주로 인식하지 못할 뿐 존재는 합니다.
3. 그럼 어떻게 “통신의 주체” 역할을 할까?
- TCP/HTTP 프로토콜을 보면, 클라이언트가 서버에 요청(HTTP Request)을 보내면, 서버가 그 연결을 통해 응답(HTTP Response)을 돌려보냅니다.
- 양쪽 모두 연결이 맺어진 상태에서 상대방 IP/포트를 알고 있기 때문에, 상호 간에 데이터를 주고받는 주체가 됩니다.
- WebSocket도 마찬가지로, 초기 HTTP 핸드셰이크 후에 WebSocket 연결이 열리면 양방향 통신이 이루어집니다. 이때에도 서버는 고정된 포트(예: 5173), 브라우저는 임시 포트를 통해 연결이 된 상태가 유지됩니다.
4. 간단한 예시
- 브라우저가 http://localhost:5173로 접근
- 실제로는 http://127.0.0.1:5173로 가도 같은 의미
- 이때 브라우저는 자동으로 임시 포트를 얻어, 서버(5173)로 TCP 연결을 연다.
- 서버는 127.0.0.1:52983 같은 클라이언트의 소켓 주소를 인지하고, 그 경로로 응답을 보낸다.
- 그 결과 웹 페이지가 표시됨.
- WebSocket 연결 시에는 같은 TCP 커넥션을 업그레이드하거나, 혹은 새 TCP 연결(새 임시 포트)을 여는 식으로 진행해, 양방향 데이터를 주고받게 된다.
#6 청크 나누기 최적화 (GPT 답변)
Vite는 내부적으로 Rollup을 사용하기 때문에, 청크 분할(Chunk Splitting)과 관련된 설정 역시 Rollup의 옵션을 활용해서 커스터마이징할 수 있습니다.
대표적으로 build.rollupOptions.output.manualChunks 옵션을 사용하여 특정 기준에 따라 모듈들을 분리할 수 있습니다.
아래는 예시 설정과 함께 간단한 설명을 드립니다.
1. 기본 설정 예시
vite.config.js 예시
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
output: {
/**
* manualChunks를 사용하면 특정 로직에 따라
* 모듈을 분리해서 빌드할 수 있습니다.
*/
manualChunks(id) {
// node_modules 안에 있는 패키지들은
// vendor 라는 청크로 분리
if (id.includes('node_modules')) {
return 'vendor';
}
// 예: 특정 라이브러리를 별도 청크로 분리하고 싶다면
// if (id.includes('some-library')) {
// return 'some-library';
// }
// 그 외에는 별도의 청크 분리가 없는 경우 undefined
return undefined;
},
},
},
},
});
위 설정에서 manualChunks 함수는 번들에 포함될 각 모듈의 경로(id)를 기반으로 어떤 청크로 분할할지를 결정합니다.
- id.includes('node_modules') 조건을 만족하면 'vendor'라는 이름의 청크에 모아집니다.
- 그 외 모듈은 기본 청크에 그대로 포함됩니다.
이렇게 하면 node_modules에 있는 모든 패키지를 하나의 커다란 청크(번들 파일)로 묶어, 애플리케이션의 로직 코드와는 독립적으로 캐싱하기가 용이해집니다.
2. 청크 이름을 세분화하여 분할하기
만약 프로젝트 규모가 커서 특정 라이브러리들을 개별로 분리하고 싶다면, 아래처럼 조건을 더 세분화할 수도 있습니다.
manualChunks(id) {
if (id.includes('react') || id.includes('react-dom')) {
// react 관련 모듈들을 따로 분리
return 'react-vendor';
} else if (id.includes('vue')) {
// vue 관련 모듈들을 따로 분리
return 'vue-vendor';
} else if (id.includes('node_modules')) {
// 그 외 node_modules 패키지들은 vendor로 묶기
return 'vendor';
}
}
이렇게 하면 React, Vue 각각 따로 청크가 생기고, 나머지 node_modules는 vendor라는 청크를 사용합니다.
3. 청크 사이즈 경고 기준 변경
Vite에서는 빌드 시에 생성되는 청크의 크기가 일정 기준을 넘어가면 경고가 표시됩니다.
만약 청크 사이즈가 클 수밖에 없는 상황이라면, 아래와 같이 chunkSizeWarningLimit 옵션을 조정해 줄 수도 있습니다.
export default defineConfig({
build: {
chunkSizeWarningLimit: 1000, // 단위: kb. 기본값은 500
rollupOptions: {
// ...
},
},
});
위 설정을 통해 파일 사이즈 관련 경고 기준을 조정할 수 있지만, 실제 청크 크기를 줄이려면 청크 분할 전략을 좀 더 세분화하거나, 코드 스플리팅을 적극적으로 적용하여 필요한 페이지에서만 로드되도록 설계하는 편이 좋습니다.
4. 추가적인 최적화 팁
- Dynamic import
- 페이지나 라우트별로 코드를 동적으로 import 하여, 진입점에서 불필요한 코드가 로드되지 않도록 합니다.
- 예: import('path/to/HeavyComponent.vue')
- Tree shaking
- 사용하지 않는 코드가 번들에 포함되지 않도록, ES 모듈 방식(ESM)을 최대한 활용합니다.
- Cache 전략
- 청크 분할을 통해 라이브러리(예: vendor)와 애플리케이션 코드(메인 청크)를 분리해두면, 라이브러리가 자주 바뀌지 않는 경우 사용자의 브라우저 캐싱 효율을 높일 수 있습니다.
- CDN 사용
- 특정 라이브러리를 CDN으로 분리할 수 있다면, 번들 크기를 더 줄일 수 있습니다(단, 이 경우 네트워크 상황, CORS 설정 등을 신경 써야 합니다).
정리
- build.rollupOptions.output.manualChunks 를 통해 코드 분할 기준을 직접 지정할 수 있습니다.
- 라이브러리, 자주 변하지 않는 모듈, 자주 쓰는 유틸 등으로 분리함으로써 빌드 성능과 로드 성능, 캐싱 효율을 높일 수 있습니다.
- 동적 로딩(Dynamic Import), 캐싱 전략, Tree shaking 등의 일반적인 최적화 기법과 함께 적용하면 더욱 효율적으로 번들 크기를 줄일 수 있습니다.
위와 같은 설정을 통해 프로젝트 규모나 사용 패턴에 맞춰 청크를 적절히 나누시면, 빌드 및 로드 성능 최적화에 많은 도움이 될 것입니다.
#7 vite의 빌드 출력을 분석하는 방법
위처럼 청크를 쪼개는 이유는 빌드를 분석하기 위해서다.
'프로젝트 기록 > 자율 프로젝트' 카테고리의 다른 글
개발 리뷰 -9- 디바운스 처리를 위한 지연 검색 (0) | 2025.05.13 |
---|---|
개발 리뷰 -7- vite proxy를 통한 개발 환경 인증 설정 (0) | 2025.05.02 |