Skip to content

Tutorial: Add Compression to Your Server ​

In this tutorial you will add HTTP compression to a Remix server, verify it is working with curl, and customize the compression settings.

Prerequisites ​

Step 1: Add the Compression Middleware ​

Open your server file and import the compression middleware. Add it to the middleware array in your router:

ts
import { createRouter } from 'remix/fetch-router'
import { route } from 'remix/fetch-router/routes'
import { compression } from 'remix/compression-middleware'

let routes = route({
  home: '/',
})

let router = createRouter({
  middleware: [
    compression(),
  ],
})

router.get(routes.home, () => {
  // Return a large-ish response so compression kicks in
  let body = '<h1>Hello!</h1>' + '<p>Lorem ipsum dolor sit amet.</p>'.repeat(100)
  return new Response(body, {
    headers: { 'Content-Type': 'text/html' },
  })
})

That is all the code you need. The middleware handles encoding negotiation, body compression, and setting the correct response headers.

Step 2: Verify with curl ​

Start your server and use curl to confirm compression is working. The -H flag sends the Accept-Encoding header, and --compressed tells curl to decompress the response:

sh
# Request with gzip
curl -s -o /dev/null -w "Size: %{size_download} bytes\n" \
  -H "Accept-Encoding: gzip" --compressed \
  http://localhost:3000

# Request without compression
curl -s -o /dev/null -w "Size: %{size_download} bytes\n" \
  http://localhost:3000

The compressed request should show a significantly smaller download size. You can also inspect the response headers:

sh
curl -I -H "Accept-Encoding: gzip, br" http://localhost:3000

Look for the Content-Encoding: br (or gzip) and Vary: Accept-Encoding headers in the output.

Step 3: Set a Custom Threshold ​

By default, responses smaller than 1024 bytes (1 KB) are not compressed because the overhead of compression headers can outweigh the savings. You can change this:

ts
compression({
  threshold: 512, // Compress responses larger than 512 bytes
})

For APIs that return small JSON payloads, lowering the threshold may help. For large HTML pages, the default is usually fine.

Step 4: Limit Encodings ​

If your deployment environment does not support Brotli (some older Node.js versions), you can restrict the encodings:

ts
compression({
  encodings: ['gzip', 'deflate'],
})

The middleware picks the first encoding from this list that the client also supports. Order matters -- put your preferred encoding first.

Step 5: Place Compression in the Right Order ​

Compression should run early in the middleware chain so it wraps responses from all downstream middleware. However, put logging before compression so you can see the original request info:

ts
import { logger } from 'remix/logger-middleware'
import { compression } from 'remix/compression-middleware'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    logger(),          // 1. Log first (sees original request)
    compression(),     // 2. Compress everything downstream
    staticFiles('./public'),  // 3. Static files get compressed too
  ],
})

Step 6: Combine with Static Files ​

When you use compression() before staticFiles(), static assets like CSS and JavaScript files are compressed on the fly:

ts
let router = createRouter({
  middleware: [
    compression(),
    staticFiles('./public'),
  ],
})

A request for /style.css will serve the file from disk, and the compression middleware will compress the response body before it reaches the client.

What You Learned ​

  • How to add the compression middleware to a Remix router.
  • How to verify compression is working using curl.
  • How to configure the size threshold and supported encodings.
  • How middleware ordering affects which responses get compressed.

Next Steps ​

Released under the MIT License.