---
title: "Response Compression"
description: "Hono provides built-in response compression via the compress middleware, allowing applications to reduce network payload sizes by applying compression algorithms such as gzip and deflate. The middl..."
last_updated: "2026-10-12T08:14:12.769531+00:00"
canonical_url: "https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/traffic-middleware/response-compression"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/middleware/compress/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts)
- [src/adapter/aws-lambda/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/handler.ts)
- [src/middleware/secure-headers/secure-headers.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/secure-headers/secure-headers.ts)
- [src/helper/streaming/sse.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/streaming/sse.ts)
- [src/middleware/etag/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts)
- [src/jsx/streaming.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/streaming.ts)
- [src/helper/streaming/stream.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/streaming/stream.ts)
- [src/middleware/cache/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts)
- [src/utils/compress.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/compress.ts)
- [src/middleware/etag/digest.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/digest.ts)
- [src/helper/ssg/ssg.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts)
- [src/context.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/context.ts)
- [src/helper/streaming/text.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/streaming/text.ts)
- [src/request.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/request.ts)
- [src/helper/proxy/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/proxy/index.ts)
- [src/adapter/deno/websocket.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/deno/websocket.ts)
- [src/utils/stream.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/stream.ts)
- [src/adapter/lambda-edge/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/lambda-edge/handler.ts)
- [src/middleware/serve-static/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/serve-static/index.ts)
- [src/middleware/body-limit/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/body-limit/index.ts)
- [src/middleware/jsx-renderer/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jsx-renderer/index.ts)
- [src/adapter/bun/websocket.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/websocket.ts)
- [src/middleware/timing/timing.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/timing/timing.ts)
- [src/middleware/language/language.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/language/language.ts)
- [src/client/fetch-result-please.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts)
- [src/adapter/cloudflare-pages/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-pages/handler.ts)
- [src/adapter/cloudflare-workers/websocket.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-workers/websocket.ts)
- [runtime-tests/lambda/stream-mock.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/runtime-tests/lambda/stream-mock.ts)
- [src/helper/websocket/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/websocket/index.ts)
- [src/utils/accept.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts)
</details>

## Overview

Hono provides built-in response compression via the `compress` middleware, allowing applications to reduce network payload sizes by applying compression algorithms such as `gzip` and `deflate`. The middleware intercepts outgoing responses after route execution, evaluates criteria such as content length thresholds, content types, and caching directives, and transforms qualifying response streams using the Web Standard `CompressionStream` API.

Sources: [src/middleware/compress/index.ts:46-121](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L46-L121)

## Middleware registration and configuration options

The `compress` middleware is configured through the `CompressionOptions` interface, which defines parameters controlling encoding selection, minimum payload size thresholds, and content type filtering. When invoked via `compress(options)`, it returns a `MiddlewareHandler` that intercepts execution and inspects response characteristics.

| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `encoding` | `'gzip' \| 'deflate'` | `undefined` | The compression scheme to allow. If omitted, both schemes are permitted. |
| `threshold` | `number` | `1024` | The minimum response body size in bytes required to trigger compression. |
| `contentTypeFilter` | `RegExp \| ((contentType: string) => boolean)` | `COMPRESSIBLE_CONTENT_TYPE_REGEX` | A regular expression or predicate function determining whether a given `Content-Type` header qualifies for compression. |

Sources: [src/middleware/compress/index.ts:16-22](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L16-L22), [src/middleware/compress/index.ts:51-54](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L51-L54), [src/middleware/compress/index.ts:70-74](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L70-L74)

```typescript
const app = new Hono()

app.use(compress())

// Compress only JSON responses
app.use(compress({ contentTypeFilter: /^application\/json/ }))

// Compress based on custom Content-Type logic
app.use(compress({ contentTypeFilter: (type) => COMPRESSIBLE_CONTENT_TYPE_REGEX.test(type) || type === "application/x-myformat" }))
```

Sources: [src/middleware/compress/index.ts:57-68](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L57-L68)

> [!NOTE]
> If the `encoding` option is omitted, the candidate list defaults to `['gzip', 'deflate']`, allowing selection based on the client's `Accept-Encoding` header with `gzip` prioritized when both are weighted equally.

Sources: [src/middleware/compress/index.ts:12](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L12), [src/middleware/compress/index.ts:52](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L52), [src/middleware/compress/index.ts:72](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L72)

## Accept-Encoding negotiation and parsing

### Parsing mechanisms and whitespace handling

Client requests specify supported compression formats using the `Accept-Encoding` header. Hono processes this string by iterating through characters, parsing comma-separated accept values, and extracting associated parameter definitions such as quality factors. During parsing, whitespace characters—specifically ASCII space (32), horizontal tab (9), line feed (10), and carriage return (13)—are inspected and skipped using dedicated utility helpers.

```typescript
const isWhitespace = (char: number): boolean =>
  char === 32 || char === 9 || char === 10 || char === 13

const consumeWhitespace = (acceptHeader: string, startIndex: number): number => {
  while (startIndex < acceptHeader.length) {
    if (!isWhitespace(acceptHeader.charCodeAt(startIndex))) {
      break
    }
    startIndex++
  }
  return startIndex
}
```

Sources: [src/utils/accept.ts:7-18](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L7-L18)

> [!NOTE]
> The `isWhitespace` helper evaluates character code points directly, allowing `consumeWhitespace` to advance the traversal index past any leading or intervening spaces, tabs, or newlines without allocating temporary substrings.

Sources: [src/utils/accept.ts:7-18](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L7-L18)

### Call-chain execution walkthrough

When a response is evaluated for compression, the execution flow traverses through specific parsing and selection steps:

1. `compress` inspects the response and retrieves the `Accept-Encoding` header from the request object.
Sources: [src/middleware/compress/index.ts:103-104](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L103-L104)

2. `selectEncoding` receives the header string and candidate array, invoking `parseAccept` to transform the raw header into structured objects.
Sources: [src/middleware/compress/index.ts:24-31](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L24-L31)

3. `parseAccept` iterates through the header string length, calling `getNextAcceptValue` at each position.
Sources: [src/utils/accept.ts:211-222](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L211-L222)

4. `getNextAcceptValue` calls `consumeWhitespace` to locate type tokens and invokes `getNextParam` to parse key-value parameter pairs.
Sources: [src/utils/accept.ts:168-199](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L168-L199)

5. `getNextParam` initiates parameter scanning by calling `consumeWhitespace` on the remaining substring.
Sources: [src/utils/accept.ts:66-70](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L66-L70)

6. `consumeWhitespace` invokes `isWhitespace` for each character code encountered during index advancement.
Sources: [src/utils/accept.ts:10-16](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L10-L16)

7. `isWhitespace` checks whether the character code matches ASCII space, tab, line feed, or carriage return.
Sources: [src/utils/accept.ts:7-8](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L7-L8)

```mermaid
sequenceDiagram
    participant compress as compress/index.ts
    participant selectEncoding as compress/index.ts (selectEncoding)
    participant parseAccept as utils/accept.ts (parseAccept)
    participant getNextAcceptValue as utils/accept.ts (getNextAcceptValue)
    participant getNextParam as utils/accept.ts (getNextParam)
    participant consumeWhitespace as utils/accept.ts (consumeWhitespace)
    participant isWhitespace as utils/accept.ts (isWhitespace)

    compress->>selectEncoding: selectEncoding(header, candidates)
    selectEncoding->>parseAccept: parseAccept(header)
    parseAccept->>getNextAcceptValue: getNextAcceptValue(acceptHeader, i)
    getNextAcceptValue->>getNextParam: getNextParam(acceptHeader, i)
    getNextParam->>consumeWhitespace: consumeWhitespace(acceptHeader, startIndex)
    consumeWhitespace->>isWhitespace: isWhitespace(char)
```

Sources: [src/middleware/compress/index.ts:24-31](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L24-L31), [src/utils/accept.ts:7-18](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L7-L18), [src/utils/accept.ts:66-70](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L66-L70), [src/utils/accept.ts:168-199](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L168-L199), [src/utils/accept.ts:211-222](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L211-L222)

## Quality factors and encoding selection

### Encoding candidate evaluation and priority calculation

When selecting an encoding, `selectEncoding` examines the parsed accept entries against the available compression candidates (`gzip` and `deflate`). If the `Accept-Encoding` header is undefined, the function immediately returns `undefined`. Otherwise, it invokes `parseAccept` and inspects any wildcard (`*`) quality factor (`q`) value present in the request.

```typescript
const selectEncoding = (
  header: string | undefined,
  candidates: readonly Encoding[]
): Encoding | undefined => {
  if (header === undefined) {
    return undefined
  }
  const accepts = parseAccept(header)
  const wildcardQ = accepts.find((a) => a.type === '*')?.q
  let best: { encoding: Encoding; q: number } | undefined
```

Sources: [src/middleware/compress/index.ts:24-33](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L24-L33)

For each candidate encoding, the parser checks for an explicit match against the candidate name (case-insensitively). If an explicit directive is absent, it falls back to the wildcard quality factor or `0`. If any candidate yields a quality factor of `1`, iteration halts immediately and that encoding is returned. Otherwise, the candidate with the highest positive quality factor is selected as the best encoding.

```typescript
  for (const enc of candidates) {
    const explicit = accepts.find((a) => a.type.toLowerCase() === enc)
    const q = explicit ? explicit.q : (wildcardQ ?? 0)
    if (q === 1) {
      return enc
    } else if (q > 0 && (!best || q > best.q)) {
      best = { encoding: enc, q }
    }
  }
  return best?.encoding
}
```

Sources: [src/middleware/compress/index.ts:34-44](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L24-L44)

> [!NOTE]
> The evaluation loop short-circuits upon encountering the first candidate with a quality factor equal to `1`, prioritizing faster decision-making for fully preferred client encodings without scanning remaining candidates.

Sources: [src/middleware/compress/index.ts:37-38](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L37-L38)

### Quality factor sanitization rules

The `parseQuality` function normalizes raw string quality values extracted from accept parameters into standard numeric scores between `0` and `1`. Missing or empty parameter values default to `1`. Special string literals and out-of-range numbers are sanitized according to the fallback rules defined below.

| Input Parameter (`qVal`) | Resulting Score | Handling Rule |
| :--- | :--- | :--- |
| `undefined` or `''` | `1` | Default weight when parameter is omitted or empty |
| `'NaN'` or `NaN` | `1` | Invalid string or NaN numerical results default to `1` |
| `Infinity` | `1` | Positive infinity normalizes to maximum priority `1` |
| `-Infinity` | `0` | Negative infinity normalizes to minimum priority `0` |
| `< 0` or `> 1` | `1` | Out-of-bounds numeric values default to `1` |
| Valid number string (`0.0`–`1.0`) | Parsed Number | Converted directly via `Number(qVal)` |

```typescript
const parseQuality = (qVal?: string): number => {
  if (qVal === undefined) {
    return 1
  }
  if (qVal === '') {
    return 1
  }
  if (qVal === 'NaN') {
    return 0
  }

  const num = Number(qVal)
  if (num === Infinity) {
    return 1
  }
  if (num === -Infinity) {
    return 0
  }
  if (Number.isNaN(num)) {
    return 1
  }
  if (num < 0 || num > 1) {
    return 1
  }

  return num
}
```

Sources: [src/utils/accept.ts:240-266](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L240-L266)

> [!WARNING]
> Passing an out-of-bounds quality factor greater than `1` or less than `0` does not reject the encoding; instead, the sanitizer falls back to a score of `1`, treating the directive as fully preferred.

Sources: [src/utils/accept.ts:261-263](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L261-L263)

## Header sanitation and malformed tokens

### Call-chain execution walkthrough

When client request headers contain malformed directives or unexpected token structures, the parsing engine relies on dedicated recovery routines to bypass syntax errors without throwing exceptions. The execution flow follows a specific call-chain during error handling: `compress` invokes `selectEncoding`, which calls `parseAccept`, which iterates via `getNextAcceptValue`, and finally branches into `skipInvalidAcceptValue` when an empty or broken token is encountered.

1. `compress` initiates the validation check on the incoming request header.
   Sources: [src/middleware/compress/index.ts:103-104](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L103-L104)
2. `selectEncoding` receives the header string and delegates parsing to `parseAccept`.
   Sources: [src/utils/accept.ts:211-222](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L211-L222)
3. `parseAccept` advances through the buffer by invoking `getNextAcceptValue` at each index offset.
   Sources: [src/utils/accept.ts:221-222](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L221-L222)
4. `getNextAcceptValue` scans character codes for delimiters such as semicolons and commas; if a type segment is empty when a separator is reached, it passes control to `skipInvalidAcceptValue`.
   Sources: [src/utils/accept.ts:179-182](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L179-L182)
5. `skipInvalidAcceptValue` tracks quotation boundaries and escape characters (`\` at code 92, quotes at code 34) until it finds a comma (code 44) or reaches the end of the header string, safely discarding the malformed segment.
   Sources: [src/utils/accept.ts:46-64](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L46-L64)

```mermaid
sequenceDiagram
    participant compress as compress
    participant selectEncoding as selectEncoding
    participant parseAccept as parseAccept
    participant getNextAcceptValue as getNextAcceptValue
    participant skipInvalidAcceptValue as skipInvalidAcceptValue

    compress->>selectEncoding: selectEncoding(accepted, candidates)
    selectEncoding->>parseAccept: parseAccept(header)
    parseAccept->>getNextAcceptValue: getNextAcceptValue(acceptHeader, i)
    getNextAcceptValue->>skipInvalidAcceptValue: skipInvalidAcceptValue(acceptHeader, i)
```

Sources: [src/middleware/compress/index.ts:103-104](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L103-L104), [src/utils/accept.ts:46-64](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L46-L64), [src/utils/accept.ts:179-182](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L179-L182), [src/utils/accept.ts:211-222](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L211-L222)

### Recovery logic and token boundary tracking

The token scanning mechanism uses character code inspections to handle quoted strings and escape sequences safely. Inside `skipInvalidAcceptValue`, the `inQuotes` boolean flag toggles whenever a double quote (`"`, code 34) is encountered outside an escape context. When `inQuotes` is active, backslash characters (`\`, code 92) trigger an index increment that consumes the following character as an escaped literal, preventing premature quote termination.

```typescript
const skipInvalidAcceptValue = (acceptHeader: string, startIndex: number): number => {
  let i = startIndex
  let inQuotes = false
  while (i < acceptHeader.length) {
    const char = acceptHeader.charCodeAt(i)
    if (inQuotes && char === 92) {
      // '\' => escape
      i++
    } else if (char === 34) {
      // '"' => toggle quotes
      inQuotes = !inQuotes
    } else if (!inQuotes && char === 44) {
      // ',' => next accept value
      return i + 1
    }
    i++
  }
  return i
}
```

Sources: [src/utils/accept.ts:46-64](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L46-L64)

When parameter parsing encounters an unexpected assignment or malformed key structure, helper functions such as `skipInvalidParam` locate the next delimiter to resume normal iteration. If an equals sign appears where a parameter value is expected, or if parameters lack proper assignment tokens, parsing skips forward to the next semicolon or comma.

> [!CAUTION]
> Unescaped backslashes inside quoted accept parameters can offset quote toggling logic; the parser handles escape sequences explicitly by advancing past backslash characters during quoted string traversal.

Sources: [src/utils/accept.ts:50-53](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts#L50-L53)

## Response transformation and stream piping

### Evaluating response criteria before compression

Before transforming a response body, the `compress` middleware inspects response headers and request methods to ensure safety and eligibility. Compression is bypassed if the response already contains a `Content-Encoding` or `Transfer-Encoding` header, if the request method is `HEAD`, if the content length falls below the configured threshold, or if `shouldTransform` detects a `Cache-Control: no-transform` directive. Furthermore, `shouldCompress` checks the response `Content-Type` against the `contentTypeFilter` option or the default `COMPRESSIBLE_CONTENT_TYPE_REGEX` pattern.

```typescript
const contentLength = ctx.res.headers.get('Content-Length')

// Check if response should be compressed
if (
  ctx.res.headers.has('Content-Encoding') || // already encoded
  ctx.res.headers.has('Transfer-Encoding') || // already encoded or chunked
  ctx.req.method === 'HEAD' || // HEAD request
  (contentLength && Number(contentLength) < threshold) || // content-length below threshold
  !shouldCompress(ctx.res) || // not compressible type
  !shouldTransform(ctx.res) // cache-control: no-transform
) {
  return
}
```

Sources: [src/middleware/compress/index.ts:89-101](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L89-L101)

### Piping through CompressionStream and header adjustments

Once eligibility is confirmed and a valid encoding is negotiated, the middleware instantiates a web standard `CompressionStream` with the selected encoding type (`gzip` or `deflate`). The original response body is piped through the compression stream via `ctx.res.body.pipeThrough(stream)`, and a new `Response` object replaces `ctx.res`. The `Content-Length` header is removed because compressed stream output changes the payload size, while `Content-Encoding` is updated to match the chosen algorithm. Additionally, if a strong `ETag` header is present, it is converted into a weak `ETag` prefixed with `W/` since the compressed representation is not byte-identical to the original source.

```typescript
// Compress the response
const stream = new CompressionStream(encoding)
ctx.res = new Response(ctx.res.body.pipeThrough(stream), ctx.res)
ctx.res.headers.delete('Content-Length')
ctx.res.headers.set('Content-Encoding', encoding)

// Convert strong ETag to weak ETag since compressed content is not byte-identical
const etag = ctx.res.headers.get('ETag')
if (etag && !etag.startsWith('W/')) {
  ctx.res.headers.set('ETag', `W/${etag}`)
}
```

Sources: [src/middleware/compress/index.ts:109-119](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L109-L119)

> [!WARNING]
> Strong ETags must be converted to weak ETags (`W/`) following compression because on-the-fly transformations alter byte-level payload hashes, which would otherwise invalidate conditional request validations against uncompressed caches.

Sources: [src/middleware/compress/index.ts:115-119](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts#L115-L119)

## Related

- [ETag and Cache](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/traffic-middleware/etag-and-cache)
- [Streaming and SSE](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/helpers-and-tooling/streaming-and-sse)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/llms.txt) for all pages in this wiki.
