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.