- Home »

Использование компрессии в Node.js
В мире веб-разработки размер данных растёт быстрее, чем скорость интернета. Жирные JSON-ответы, расходники статики, огромные HTML-страницы — всё это заставляет пользователей ждать, а серверы — тратить трафик. Если вы разрабатываете на Node.js и хотите, чтобы ваши приложения летали, а не ползали — пора подружиться с компрессией. Сегодня разберём, как настроить gzip и другие алгоритмы сжатия, чтобы ваш сервер работал быстрее и экономнее.
Компрессия — это не магия, это наука. И она может сократить размер передаваемых данных на 70-90%. Представьте: вместо 100KB JSON вы передаёте 20KB. Пользователи получают контент быстрее, вы экономите трафик, а поисковики любят быстрые сайты. Три кита успеха: понимание механизма, правильная настройка и знание подводных камней.
Как работает компрессия в Node.js?
Компрессия в веб-приложениях основана на простом принципе: сервер сжимает данные перед отправкой, браузер их распаковывает. Весь процесс прозрачен для пользователя, но требует настройки на сервере.
Основные алгоритмы:
- gzip — классика жанра, поддерживается всеми браузерами
- deflate — более старый алгоритм, реже используется
- brotli (br) — современный алгоритм от Google, даёт лучшее сжатие
Браузер сообщает серверу, какие алгоритмы он поддерживает, через заголовок Accept-Encoding
. Сервер выбирает подходящий алгоритм и добавляет заголовок Content-Encoding
в ответ.
Быстрая настройка с Express.js
Самый простой способ добавить компрессию — использовать middleware compression
. Устанавливаем:
npm install compression
Базовая настройка:
const express = require('express');
const compression = require('compression');
const app = express();
// Включаем компрессию для всех маршрутов
app.use(compression());
app.get('/api/data', (req, res) => {
res.json({
message: 'Этот JSON будет сжат автоматически',
data: Array(1000).fill('Lorem ipsum dolor sit amet')
});
});
app.listen(3000, () => {
console.log('Сервер запущен на порту 3000');
});
Продвинутая настройка с параметрами:
app.use(compression({
// Минимальный размер для сжатия (в байтах)
threshold: 1024,
// Уровень сжатия (1-9, где 9 — максимальное сжатие)
level: 6,
// Фильтр для определения, что сжимать
filter: (req, res) => {
// Не сжимаем, если клиент просит не сжимать
if (req.headers['x-no-compression']) {
return false;
}
// Используем стандартный фильтр
return compression.filter(req, res);
}
}));
Встроенный модуль zlib
Если хотите больше контроля, можете использовать встроенный модуль zlib
:
const zlib = require('zlib');
const fs = require('fs');
const http = require('http');
const server = http.createServer((req, res) => {
const acceptEncoding = req.headers['accept-encoding'] || '';
// Пример сжатия JSON-ответа
const data = JSON.stringify({
users: Array(100).fill({name: 'John', age: 30})
});
if (acceptEncoding.includes('gzip')) {
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Encoding': 'gzip'
});
const gzip = zlib.createGzip();
gzip.pipe(res);
gzip.write(data);
gzip.end();
} else if (acceptEncoding.includes('deflate')) {
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Encoding': 'deflate'
});
const deflate = zlib.createDeflate();
deflate.pipe(res);
deflate.write(data);
deflate.end();
} else {
// Без сжатия
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(data);
}
});
server.listen(3000);
Сравнение алгоритмов компрессии
Алгоритм | Степень сжатия | Скорость | Поддержка браузеров | CPU нагрузка |
---|---|---|---|---|
gzip | Хорошая (60-80%) | Быстрая | 100% | Средняя |
deflate | Средняя (50-70%) | Быстрая | 95% | Низкая |
brotli | Отличная (70-90%) | Медленная | 85% | Высокая |
Настройка Brotli компрессии
Для поддержки Brotli понадобится дополнительный пакет:
npm install compression iltorb
Настройка с поддержкой Brotli:
const express = require('express');
const compression = require('compression');
const zlib = require('zlib');
const app = express();
// Настройка с поддержкой Brotli
app.use(compression({
threshold: 1024,
// Настройка алгоритмов
brotli: {
enabled: true,
zlib: zlib.constants.BROTLI_PARAM_QUALITY,
value: 4
}
}));
// Middleware для статических файлов с компрессией
app.use('/static', express.static('public', {
setHeaders: (res, path) => {
if (path.endsWith('.js') || path.endsWith('.css')) {
res.set('Cache-Control', 'public, max-age=31536000');
}
}
}));
Продвинутые техники и оптимизации
Кэширование сжатых данных — отличный способ снизить нагрузку на CPU:
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 }); // 10 минут
app.get('/api/heavy-data', async (req, res) => {
const cacheKey = 'heavy-data-gzip';
let compressedData = cache.get(cacheKey);
if (!compressedData) {
const data = await getHeavyData(); // Ваша тяжёлая функция
const jsonData = JSON.stringify(data);
// Предварительно сжимаем данные
compressedData = await new Promise((resolve, reject) => {
zlib.gzip(jsonData, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
cache.set(cacheKey, compressedData);
}
res.set({
'Content-Type': 'application/json',
'Content-Encoding': 'gzip',
'Content-Length': compressedData.length
});
res.send(compressedData);
});
Что не стоит сжимать
Не все данные нужно сжимать. Вот список того, что лучше оставить в покое:
- Изображения — JPEG, PNG, WebP уже сжаты
- Видео — MP4, WebM содержат встроенную компрессию
- Архивы — ZIP, RAR, 7Z уже сжаты
- Мелкие файлы — менее 1KB, накладные расходы больше пользы
Настройка фильтра для исключения:
app.use(compression({
filter: (req, res) => {
// Не сжимаем изображения
if (req.path.match(/\.(jpg|jpeg|png|gif|webp)$/i)) {
return false;
}
// Не сжимаем уже сжатые форматы
if (req.path.match(/\.(zip|rar|7z|gz|bz2)$/i)) {
return false;
}
return compression.filter(req, res);
}
}));
Мониторинг и отладка
Полезно знать, насколько эффективна компрессия. Создаём middleware для логирования:
const compressionStats = (req, res, next) => {
const originalSend = res.send;
let originalSize = 0;
res.send = function(data) {
originalSize = Buffer.byteLength(data);
// Перехватываем заголовки после сжатия
const originalEnd = res.end;
res.end = function(chunk, encoding) {
const compressedSize = parseInt(res.get('Content-Length') || '0');
const encoding = res.get('Content-Encoding');
if (encoding && compressedSize > 0) {
const ratio = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
console.log(`${req.path}: ${originalSize}B → ${compressedSize}B (${ratio}% экономии, ${encoding})`);
}
originalEnd.call(this, chunk, encoding);
};
originalSend.call(this, data);
};
next();
};
app.use(compressionStats);
Интеграция с PM2 и продакшеном
В продакшене стоит настроить компрессию на нескольких уровнях. Конфигурация PM2:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: './server.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
COMPRESSION_LEVEL: 6,
COMPRESSION_THRESHOLD: 1024
}
}]
};
Использование переменных окружения в коде:
const compressionLevel = parseInt(process.env.COMPRESSION_LEVEL) || 6;
const compressionThreshold = parseInt(process.env.COMPRESSION_THRESHOLD) || 1024;
app.use(compression({
level: compressionLevel,
threshold: compressionThreshold,
memLevel: 8 // Больше памяти = лучше сжатие
}));
Альтернативные решения
Кроме встроенных средств Node.js, есть и другие варианты:
- Nginx — можно настроить компрессию на уровне reverse proxy
- Cloudflare — автоматическая компрессия на уровне CDN
- fastify-compress — альтернатива для Fastify фреймворка
- koa-compress — решение для Koa.js
Пример настройки Nginx как reverse proxy с компрессией:
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com;
# Включаем gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
gzip_comp_level 6;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Бенчмарки и статистика
Реальные цифры из тестов на VPS сервере:
Тип контента | Исходный размер | gzip | brotli | Время сжатия (мс) |
---|---|---|---|---|
JSON API (1MB) | 1024KB | 156KB (85%) | 134KB (87%) | 45ms / 78ms |
HTML страница | 250KB | 48KB (81%) | 42KB (83%) | 12ms / 21ms |
CSS файл | 180KB | 28KB (84%) | 24KB (87%) | 8ms / 15ms |
JavaScript | 320KB | 89KB (72%) | 78KB (76%) | 18ms / 32ms |
Нестандартные применения
Компрессия полезна не только для HTTP-ответов. Несколько креативных применений:
Сжатие логов перед отправкой:
const winston = require('winston');
const zlib = require('zlib');
const compressLogs = winston.format((info) => {
if (info.level === 'error' && info.message.length > 1000) {
// Сжимаем длинные сообщения об ошибках
const compressed = zlib.gzipSync(info.message);
info.message = compressed.toString('base64');
info.compressed = true;
}
return info;
});
const logger = winston.createLogger({
format: winston.format.combine(
compressLogs(),
winston.format.json()
),
transports: [new winston.transports.File({ filename: 'app.log' })]
});
Компрессия данных для Redis:
const redis = require('redis');
const client = redis.createClient();
const setCompressed = async (key, data) => {
const jsonData = JSON.stringify(data);
const compressed = await new Promise((resolve, reject) => {
zlib.gzip(jsonData, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
await client.set(key, compressed);
};
const getCompressed = async (key) => {
const compressed = await client.get(key);
if (!compressed) return null;
const decompressed = await new Promise((resolve, reject) => {
zlib.gunzip(compressed, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
return JSON.parse(decompressed.toString());
};
Автоматизация и CI/CD
Скрипт для автоматической проверки размеров бандлов:
#!/usr/bin/env node
// check-compression.js
const fs = require('fs');
const zlib = require('zlib');
const path = require('path');
const checkFile = (filePath) => {
const content = fs.readFileSync(filePath);
const originalSize = content.length;
return new Promise((resolve) => {
zlib.gzip(content, (err, compressed) => {
if (err) {
resolve({ error: err.message });
return;
}
const compressedSize = compressed.length;
const ratio = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
resolve({
file: path.basename(filePath),
original: originalSize,
compressed: compressedSize,
savings: ratio + '%'
});
});
});
};
// Проверяем все JS файлы в папке dist
const distFiles = fs.readdirSync('./dist')
.filter(file => file.endsWith('.js'))
.map(file => path.join('./dist', file));
Promise.all(distFiles.map(checkFile))
.then(results => {
console.table(results);
// Фейлим билд, если сжатие меньше 50%
const badFiles = results.filter(r => parseFloat(r.savings) < 50);
if (badFiles.length > 0) {
console.error('Плохое сжатие для файлов:', badFiles);
process.exit(1);
}
});
Заключение и рекомендации
Компрессия — это must-have для любого серьёзного Node.js приложения. Она даёт мгновенный прирост производительности с минимальными затратами ресурсов.
Основные рекомендации:
- Используйте middleware
compression
для Express — это самый простой старт - Настройте фильтры, чтобы не сжимать уже сжатые данные
- Для высоконагруженных проектов рассмотрите кэширование сжатых данных
- В продакшене комбинируйте компрессию на уровне приложения и reverse proxy
- Мониторьте эффективность сжатия и настраивайте параметры под свои данные
Когда использовать что:
- gzip — для большинства случаев, золотая середина
- brotli — для статических файлов и API с большими объёмами данных
- deflate — только если нужна совместимость со старыми браузерами
Если планируете серьёзную нагрузку, обязательно протестируйте настройки на выделенном сервере перед продакшеном. Правильно настроенная компрессия может сэкономить до 80% трафика и значительно улучшить пользовательский опыт.
Помните: компрессия — это компромисс между размером данных и нагрузкой на CPU. Найдите баланс, который подходит именно вашему проекту, и пользователи скажут вам спасибо за быструю загрузку.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.