go 32 lines · 1 tab

Gzip compression middleware with correct Vary header

Leah Thompson Jan 2026
1 tab
package middleware

import (
  "compress/gzip"
  "net/http"
  "strings"
)

type gzipResponseWriter struct {
  http.ResponseWriter
  w *gzip.Writer
}

func (g gzipResponseWriter) Write(b []byte) (int, error) {
  return g.w.Write(b)
}

func Gzip(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
      next.ServeHTTP(w, r)
      return
    }

    w.Header().Set("Vary", "Accept-Encoding")
    w.Header().Set("Content-Encoding", "gzip")
    gz := gzip.NewWriter(w)
    defer gz.Close()

    next.ServeHTTP(gzipResponseWriter{ResponseWriter: w, w: gz}, r)
  })
}
1 file · go Explain with highlit

Compressing responses is an easy bandwidth win for JSON APIs, but only when it's done carefully. I check Accept-Encoding for gzip, set Vary: Accept-Encoding so caches behave correctly, and stream output through gzip.Writer so we don't buffer full responses in memory. The key operational detail is CPU: compression can become expensive under load, so I usually enable it at the edge or for selected endpoints, not blindly everywhere. This pattern is also safe for streaming responses because it writes as data is produced. If you add content-type checks and a size threshold, you can avoid compressing already-compressed formats like image/*. Done right, this is a clean middleware that improves latency and reduces egress costs.


Related snips

Share this code

Here's the card — post it anywhere.

Gzip compression middleware with correct Vary header — share card
Link copied