Serving Compressed Pages Through Go Templates and Chi Router

How to make the Chi Router's Compress middleware work for template based responses.

Serving Compressed Pages Through Go Templates and Chi Router
Photo by Safar Safarov / Unsplash

TL;DR

To make the Chi Router's Compress middleware work for template based responses, you have to make sure you add the following line in your html processor.

w.Header().Set("Content-Type", "text/html")

The other option it to make sure you have the string text/html; charset=utf-8 passed in when declaring the middleware.

var compressibleContentTypes = []string{
    "text/html",
    "text/html; charset=utf-8",
    // other types like css, js etc
}

r := chi.NewRouter()
r.Use(middleware.Compress(5), compressibleContentTypes)

It seems like a trivial thing, but I spent a few hours trying to make the compress middleware in Chi work with Go's templating system. I thought I'd document it in case it helps anyone else.

Details

Let's start with some simple test code that serves content from a static folder in addition to a page rendered by Go's html templating system.

It declares the use of the compress middleware simply as

r.Use(middleware.Compress(5))
package main

import (
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"html/template"
	"net/http"
)

func main() {
	// Define the directory where your static files are located
	fs := http.FileServer(http.Dir("files/"))

	r := chi.NewRouter()
	r.Use(middleware.Logger)
	r.Use(middleware.Compress(5))

	r.Get("/hey", templateResponse)
	r.Handle("/static/*", http.StripPrefix("/static/", fs))

	http.ListenAndServe(":3000", r)
}

func templateResponse(w http.ResponseWriter, r *http.Request) {
	tmpl, err := template.ParseFiles("templates/test.tmpl.html")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	data := struct {
		Title string
	}{
		Title: "Hello from Go!",
	}

	err = tmpl.Execute(w, data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

Simple Test Code for a Go WebApp Using Chi Router

If you spin this code up on your machine and use Developer Tools in your browser, you will notice that all compressible content under /static/* is successfully gzipped, but the response from /hey is not.

If you look closely in the Developer Tool's Network tab and inspect the response from /hey, you will see two things.

  • The response is not compressed
  • The response is using the value of Content-Type: text/html; charset=utf-8 in the response header.
Screenshot of the Network Tab of Developer Tools

Hence, the compress middleware is ignoring this response and it's because of this header. Chi's middleware is expecting it to simply be text/html . Take a look at the following snippet at https://github.com/go-chi/chi/blob/master/middleware/compress.go

var defaultCompressibleContentTypes = []string{
	"text/html",
	"text/css",
	"text/plain",
	"text/javascript",
	"application/javascript",
	"application/x-javascript",
	"application/json",
	"application/atom+xml",
	"application/rss+xml",
	"image/svg+xml",
}

Also, later down in the code is the following bit of instruction...

// NOTE: make sure to set the Content-Type header on your response
// otherwise this middleware will not compress the response body. For ex, in
// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody))
// or set it manually.

Hence, you simply add the following line before rendering the template, it addresses the issue.

    w.Header().Set("Content-Type", "text/html")  // <-----------
    err = tmpl.Execute(w, data)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

Now when you run the webapp and look at the Developer Tools, you will see that the repones is gzipped!

Screenshot of the Network Tab of Developer Tools After The Fix