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
- A Remix project with a working server (see the Getting Started guide).
Step 1: Add the Compression Middleware
Open your server file and import the compression middleware. Add it to the middleware array in your router:
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:
# 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:3000The compressed request should show a significantly smaller download size. You can also inspect the response headers:
curl -I -H "Accept-Encoding: gzip, br" http://localhost:3000Look 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:
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:
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:
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:
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
compressionmiddleware 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
- API Reference -- Full details on options and behavior.
- static-middleware -- Serve static files alongside compression.
- Middleware -- How middleware works in Remix.