컨텐츠를 불러오는 중...
server.middlewareMode : true
http://175.45.205.245/voting/3badeb16-38d3-4add-8486-47f4ee431024/waiting
라는 url을 통해서 접근을 하고자 할 경우 해당 URL은 index.html 이 등록되어 있는 경로가 아니기 때문에 브라우저는 이 URL에 대한 자원을 서버에 직접 요청을 보내게 됩니다.브라우저 요청: /dashboard
↓
Vite 미들웨어:
1. /dashboard 실제 파일 확인 → 없음
2. index.html 반환
↓
브라우저:
1. index.html 로드
2. JavaScript 실행
3. 라우터가 /dashboard URL 확인
4. 해당 컴포넌트 렌더링
app.use(express.static("public"));
app.use(express.static("uploads"));
app.use(express.static("files"));
트리 쉐이킹
을 하고, 코드 분할
을 하는 등의 작업에 대해서 설명하지 않고 오로지 배포의 관점에서 설명 해보겠습니다.node_modules
폴더 내에서도 프로젝트를 진행함에 있어서 주로 사용되는 패키지인 tanstack
, react
,socket
모듈들 별도의 청크로 관리하여 빌드 시 성능을 향상 시킵니다.캐시 검증
매커니즘을 수행할 때 이점을 갖습니다.vendor-react-[hash].js
와 같은 형식으로 파일이 생성됩니다.ETag
을 활용하는 전략이 적극적으로 사용될 수 있습니다. ETag는 특정 버전의 리소스를 식별하는 고유한 식별자입니다. Vite가 빌드를 수행할 때, 각 청크(chunk)의 내용을 기반으로 해시값을 생성하고, 이 해시값이 ETag로 사용됩니다. 이는 마치 각 코드 조각에 고유한 지문을 부여하는 것과 같습니다. 이러한 내용을 배경으로 다시 위의 시나리오를 반복해서 생각 해보겠습니다.If-None-Match
헤더에 저장된 ETag를 포함하여 요청을 보냅니다. 파일이 변경되지 않았다면(ETag가 동일하다면), 서버는 304 Not Modified 응답을 보낸 후 브라우저에서 캐시된 버전을 사용할 수 있기 때문에 코드 분할이 브라우저의 캐싱 전략에 큰 영향을 줄 수 있습니다./assets/images/
라는 경로를 보고 즉시 이미지 파일을 찾아야 한다는 것을 알 수 있습니다. 그렇기 때문에 서버가 다른 종류의 요청(API 호출 등)을 처리하는 미들웨어를 거치지 않고 바로 정적 파일 서빙 레이어로 요청을 라우팅할 수 있게 합니다CORS
와 프록시 서버
의 관계를 이해하기 위해, 먼저 웹 브라우저의 동작 방식부터 살펴보겠습니다.브라우저의 Same-Origin Policy
는 보안을 위해 다른 출처(도메인, 포트, 프로토콜)로의 HTTP 요청을 기본적으로 제한합니다. 예를 들어, localhost:3000에서 실행되는 프론트엔드 애플리케이션이 api.example.com으로 직접 요청을 보내려고 하면 CORS 오류가 발생합니다.preflight
요청을 보낸다는 것입니다.preflight
입니다. 메서드와 헤더를 확인하여 실제로 작업이 수행 가능한지의 여부를 먼저 체크하는데 이 경우에는 CORS 가 허용이 되었는 지 확인을 하는 것입니다.http://
, https://
)example.com
, api.example.com
)80
, 443
)동일 출처
로 판단이 되었을 경우 자원의 요청에 대해서 제한을 두지 않습니다. 그렇기 때문에 브라우저(localhost:3000)
에서 프록시 서버(localhost:3000)
로 요청을 보내도 어떠한 문제도 일어나지 않고 자원을 요청하고 응답 받을 수 있습니다.project/
├── src/
│ ├── components/
│ ├── assets/
│ └── App.vue
├── public/
├── package.json
└── vite.config.js
dist/
├── index.html (최적화된 HTML)
└── assets/
├── js/ (번들링된 JavaScript)
├── css/ (최적화된 CSS)
└── images/ (최적화된 이미지)
app-bundle-[hash].js (5MB)
- React (2MB)
- Redux (1MB)
- 비즈니스 로직 (2MB)
vendor-react-[hash1].js (2MB)
vendor-redux-[hash2].js (1MB)
business-logic-[hash3].js (2MB)
app-bundle-[hash].js (5MB)
- React (2MB)
- Redux (1MB)
- 비즈니스 로직 (2MB)
ETag : [hash]
vendor-react-[hash1].js (2MB)
vendor-redux-[hash2].js (1MB)
business-logic-[hash3].js (2MB)
# Nginx 설정 예시
location /assets/images/ {
expires 10d;
add_header Cache-Control "public, no-transform";
}
location /assets/fonts/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
브라우저(localhost:3000) → API 서버(api.example.com)
1. 브라우저가 요청을 보내기 전에 Same-Origin Policy 검사
2. 출처가 다르면 브라우저가 preflight 요청(OPTIONS)을 보냄
3. 서버의 CORS 헤더를 확인하고 허용된 경우에만 실제 요청 진행
브라우저(localhost:3000) → 프록시 서버(localhost:3000) → API 서버(api.example.com)
1. 브라우저는 같은 출처의 프록시 서버와 통신
2. 프록시 서버는 브라우저가 아닌 일반 HTTP 클라이언트로서 API 서버와 통신
브라우저가 http://localhost:3000/api/users로 요청
↓
Vite 개발 서버가 이 요청을 가로챔
↓
프록시 설정을 확인하고 '/api'로 시작하는 요청임을 인식
↓
요청을 env.SOCKET_URL + '/api/users'로 변환
↓
프록시 서버가 실제 API 서버로 요청을 전달
↓
changeOrigin: true로 인해 Host 헤더가 대상 서버에 맞게 변경됨
↓
API 서버는 마치 직접적인 요청처럼 받아들이고 처리
↓
API 서버가 응답을 프록시 서버로 전송
↓
프록시 서버가 응답 헤더를 수정 (cache-control 등)
↓
최종적으로 브라우저로 응답 전달
manualChunks(id) {
if (id.includes("node_modules")) {
if (id.includes("@tanstack")) return "vendor-tanstack";
if (id.includes("react")) return "vendor-react";
if (id.includes("@socket")) return "vendor-socket";
return "vendor";
}
if (id.includes("/features/")) {
const feature = id.split("/features/")[1].split("/")[0];
return `feature-${feature}`;
}
}
assetFileNames: (assetInfo) => {
const fileName = assetInfo.name || "unknown";
const extType = fileName.split(".").pop()?.toLowerCase() || "";
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
return `assets/images/[name]-[hash][extname]`;
}
if (/glb|hdr/i.test(extType)) {
return `assets/models/[name]-[hash][extname]`;
}
if (/woff2?|ttf|eot|otf/i.test(extType)) {
return `assets/fonts/[name]-[hash][extname]`;
}
return `assets/[name]-[hash][extname]`;
},
server: {
port: 3000,
proxy: {
"/api": {
target: env.SOCKET_URL,
changeOrigin: true,
secure: false,
ws: true,
configure: (proxy) => {
proxy.on("proxyRes", (proxyRes) => {
proxyRes.headers["cache-control"] = "public, max-age=31536000";
});
},
},
},
middlewareMode: env.NODE_ENV === "development" ? false : true,
},