zlib.brotliCompressSync()
Compresses data synchronously with Brotli, typically producing smaller output than gzip on text.
zlib.brotliCompressSync(buffer[, options])- Added in Node.js v11.7.0, v10.16.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$buffer | Buffer | TypedArray | DataView | ArrayBuffer | string | The data to compress. A string is encoded as UTF-8 first. |
$optionsoptional | Object | Brotli options: `flush`, `finishFlush`, `chunkSize`, `params` (an object keyed by `zlib.constants.BROTLI_PARAM_*`), `info` and `maxOutputLength`. |
Return value
BufferA Buffer containing the Brotli-compressed data. With `options.info` set to `true`, an object `{ buffer, engine }` is returned instead.
Errors
Throws synchronously if a Brotli parameter is out of range or compression fails, and `ERR_BUFFER_TOO_LARGE` when the output exceeds `maxOutputLength`.
Changelog
- PHP v14.5.0, v12.19.0The `maxOutputLength` option is supported.
zlib.brotliCompressSync() compresses with Brotli, the algorithm behind Content-Encoding: br. On HTML, CSS, JavaScript and JSON it typically produces 15–25% smaller output than gzip at comparable settings, partly because it ships with a built-in dictionary of common web text.
Quality is the setting that matters
Brotli's quality parameter runs from 0 to 11 and defaults to 11 — the maximum. That default is dramatically slower than gzip, sometimes by an order of magnitude, which is why Brotli sometimes gets a reputation for being unusably slow. Set it explicitly:
- Quality 0–4 — fast enough for per-request compression, still competitive with gzip.
- Quality 5–6 — a reasonable balance for dynamic responses.
- Quality 11 — build-time only, for static assets compressed once and served many times.
Setting BROTLI_PARAM_SIZE_HINT to the input length lets the encoder allocate better and improves the ratio slightly on larger inputs.
What trips people up
Only serve br to clients that advertise it in Accept-Encoding; an old client receiving Brotli sees binary garbage. And since the call blocks, high quality levels on a large buffer will stall the event loop — do that work at build time or in the async zlib.brotliCompress().
Example
When to use it
- Precompress static bundles at build time so a CDN can serve Content-Encoding: br
- Compress a JSON API response at quality 4 where CPU per request matters
- Compare Brotli against gzip on real payloads before switching formats
- Store archived text records where the extra ratio pays for itself
Frequently asked questions
Is Brotli better than gzip in Node.js?
Brotli usually produces 15-25% smaller output than gzip on text such as HTML, CSS, JS and JSON, but its default quality of 11 is far slower to compress. Lower BROTLI_PARAM_QUALITY to 4 or 5 for per-request use, and reserve 11 for build-time assets.
Why is brotliCompressSync so slow?
Because it defaults to quality 11, the maximum setting, which can be an order of magnitude slower than gzip. Pass params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 4 } and it becomes comparable in speed.
How do I set the Brotli compression level in Node.js?
Use the params option with the BROTLI_PARAM_QUALITY constant, for example zlib.brotliCompressSync(buf, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 5 } }). There is no level option — that one belongs to the gzip and deflate family.
More examples
Size versus quality across the whole range
Bytes fall steeply from q0 to about q5 and then flatten, while time keeps climbing — often 20x or more between q4 and q11 for single-digit percentage gains. Read the table to find the knee for your own data; the exact milliseconds vary by machine, the shape does not.
const zlib = require('node:zlib');
const payload = Buffer.from(JSON.stringify({
users: Array.from({ length: 300 }, (_, i) => ({
id: i, name: `user-${i}`, role: 'member', active: true
}))
}));
console.log('input:', payload.length, 'bytes');
for (const q of [0, 2, 4, 6, 9, 11]) {
const t0 = process.hrtime.bigint();
const out = zlib.brotliCompressSync(payload, {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: q,
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: payload.length
}
});
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
console.log(`q${String(q).padStart(2)} ${String(out.length).padStart(6)} bytes ${ms.toFixed(1)} ms`);
}Precompress build output for a CDN
Quality 11 is the right choice here because each file is compressed once during the build and served thousands of times. Writing both .br and .gz lets the server pick by Accept-Encoding and fall back for clients that do not advertise br. This blocks the event loop for seconds on a large bundle, which is harmless in a build script and unacceptable in a request handler.
const zlib = require('node:zlib');
const fs = require('node:fs');
const path = require('node:path');
const MAX_Q = { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 };
for (const file of fs.readdirSync('dist')) {
if (!/\.(js|css|html|svg|json)$/.test(file)) continue;
const full = path.join('dist', file);
const raw = fs.readFileSync(full);
const br = zlib.brotliCompressSync(raw, {
params: { ...MAX_Q, [zlib.constants.BROTLI_PARAM_SIZE_HINT]: raw.length }
});
const gz = zlib.gzipSync(raw, { level: 9 });
fs.writeFileSync(`${full}.br`, br);
fs.writeFileSync(`${full}.gz`, gz);
console.log(`${file.padEnd(24)} ${raw.length} -> br ${br.length} / gz ${gz.length}`);
}
Discussion