Serving Compressed Pages Through Go Templates and Chi Router
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.
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!