Express.js compression 미들웨어 설정 및 최적화
Express.js에서 compression 미들웨어를 사용하여 응답 크기를 줄이고 성능을 향상시키는 방법을 다룹니다.
Introduction
웹 애플리케이션에서 응답 크기를 줄이는 것은 성능 최적화의 핵심 요소입니다. Express.js에서 compression 미들웨어를 사용하면 HTTP 응답을 자동으로 압축하여 전송 속도를 향상시킬 수 있습니다.
Environment
# 프로젝트 의존성
npm list express compression
# express@4.18.2
# compression@1.7.4
# 브라우저 개발자 도구에서 응답 헤더 확인
# Content-Encoding: gzipProblem
API 서버의 응답 시간이 느려지는 문제가 발생했습니다:
// 압축 없는 API 응답
app.get('/api/products', async (req, res) => {
const products = await Product.find().limit(1000);
// 응답 크기: 2.5MB
// 전송 시간: 약 800ms
res.json(products);
});측정 결과
# 응답 크기 확인
$ curl -I http://localhost:3000/api/products
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 2621440 # 2.5MBAnalysis
압축 전후 비교
| 항목 | 압축 전 | gzip | brotli |
|---|---|---|---|
| 크기 | 2.5MB | 450KB | 380KB |
| 전송 시간 | 800ms | 180ms | 150ms |
| CPU 사용률 | 낮음 | 중간 | 높음 |
compression 미들웨어 동작 방식
클라이언트 요청 → compression 미들웨어 → 핸들러 → 응답 압축 → 클라이언트Solution
1. 기본 compression 설정
const express = require('express');
const compression = require('compression');
const app = express();
// 모든 응답에 gzip 압축 적용
app.use(compression());
// API 라우트
app.get('/api/products', async (req, res) => {
const products = await Product.find().limit(1000);
res.json(products);
});2. 필터 함수로 특정 경로 제외
app.use(compression({
// 1KB 미만은 압축하지 않음
threshold: 1024,
// 필터 함수: 어떤 응답을 압축할지 결정
filter: (req, res) => {
// 이미 압축된 응답은 제외
if (req.headers['x-no-compression']) {
return false;
}
// 이미 Content-Encoding이 설정된 경우 제외
if (res.getHeader('Content-Encoding')) {
return false;
}
// 특정 경로 제외
if (req.path.startsWith('/static/images/')) {
return false;
}
// 기본 필터 적용
return compression.filter(req, res);
},
}));3. Brotli 압축 설정
const zlib = require('zlib');
app.use(compression({
// Brotli 사용 (지원되는 경우)
brotli: {
enabled: true,
zlib: {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 4, // 0-11, 높을수록 압축률↑ 속도↓
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: 1024 * 1024, // 1MB
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_GENERIC,
},
},
},
threshold: 1024,
}));4. Content-Type 필터링
app.use(compression({
filter: (req, res) => {
const contentType = res.getHeader('content-type') || '';
const type = contentType.toLowerCase();
// 압축 대상 Content-Type 목록
const compressibleTypes = [
'text/',
'application/json',
'application/javascript',
'application/xml',
'application/rss+xml',
'application/atom+xml',
'image/svg+xml',
];
// 압축 가능한 타입인지 확인
const shouldCompress = compressibleTypes.some(t => type.includes(t));
// 바이너리 파일은 제외 (이미 압축된 경우)
if (type.includes('image/') && !type.includes('image/svg')) {
return false;
}
if (type.includes('video/') || type.includes('audio/')) {
return false;
}
return shouldCompress;
},
threshold: 1024,
}));5. 개발 환경과 프로덕션 환경 분리
const express = require('express');
const compression = require('compression');
const app = express();
// 개발 환경에서는 압축 비활성화
if (process.env.NODE_ENV === 'production') {
app.use(compression({
level: 6, // gzip 압축 레벨 (1-9)
threshold: 1024,
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
}));
}
// 정적 파일 압축 설정
app.use('/static', express.static('public', {
maxAge: '1d',
etag: true,
lastModified: true,
}));6. 성능 측정 미들웨어
app.use((req, res, next) => {
const start = Date.now();
const originalSend = res.send;
res.send = function(body) {
const duration = Date.now() - start;
const originalSize = Buffer.byteLength(body);
const compressed = res.getHeader('Content-Encoding');
const finalSize = compressed ?
parseInt(res.getHeader('Content-Length') || '0') : originalSize;
console.log({
path: req.path,
originalSize,
compressedSize: finalSize,
compressionRatio: compressed ?
((1 - finalSize / originalSize) * 100).toFixed(2) + '%' : 'N/A',
duration: duration + 'ms',
});
originalSend.call(this, body);
};
next();
});Lessons Learned
- 적절한 threshold 설정: 너무 작은 응답은 압축하지 않아야 CPU 낭비를 방지할 수 있습니다
- Brotli 활용: 최신 브라우저에서는 Brotli가 gzip보다 더 나은 압축률을 제공합니다
- Content-Type 필터링: 바이너리 파일이나 이미 압축된 파일은 제외하세요
- 개발 환경 고려: 개발 시에는 압축을 비활성화하여 디버깅 편의성을 높이세요
- 모니터링: 압축률과 응답 시간을 지속적으로 모니터링하세요
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.