103 Early Hints in Go, or the new Way of How to Improve Performance of a Web Page written in Go
14 Nov 2022

103 Early Hints in Go, or the new Way of How to Improve Performance of a Web Page written in Go

Since Go 1.19 we can use a new 103 (Early Hints) http status code when we create web applications. Let’s figure out how and when this could help us. We are going to create a simple golang web server that servers some html content. One html page will be served with 103 header and another one without. After loading comparison we will see how early hints can improve page performance.

Early hints is a special HTTP header that is sent before the web server sends the final HTTP response to the client. At this moment it’s supported only by Chrome browser. As soon as the browser requests a page, server immediately returns 103 early hints header. In the meantime, a server will generate a usual HTTP response. This helps us utilize in maximum the loading time by letting browser know what resources it should preload while waiting for the final response from a server.

Enough theory, let’s write some code :)

First, I’m going to create an index.html with some dummy structure. Also, I will load bootsrap frontend framework to simulate some heavy css and js references during page load.

<html>
  <head>
    <title>Hello!</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
  </head>
  <body>
  <div class="p-2 bg-success">
    <h1 class="text-white">Hello!</h1>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
  </body>
</html>

Now we need to serve it. Let’s create a server.


//go:embed index.html
var index string // embeded index.html

func main() {
	log.Println("Starting server...")
    // Handler for the page without early hints.
	http.HandleFunc("/1", noHintsHandler)
    // Handler for the page with early hints
	http.HandleFunc("/2", withHintsHandler)
	if err := http.ListenAndServe(":8082", nil); err != nil {
		log.Fatal(err)
	}
}

func noHintsHandler(w http.ResponseWriter, r *http.Request) {
	t := template.New("")
	t, err := t.Parse(index)
	if err != nil {
		log.Println(err)
	}
	t.Execute(w, nil)
}

func withHintsHandler(w http.ResponseWriter, r *http.Request) {
    // Adding headers with preload information for bootstrap.min.css and bootstrap.bundle.min.js
	w.Header().Add("Link", "<https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css>; rel=preload; as=style")
	w.Header().Add("Link", "<https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js>; rel=preload; as=script")
    // Sending 103 status code.
	w.WriteHeader(http.StatusEarlyHints)

    t := template.New("")
	t, err := t.Parse(index)
	if err != nil {
		log.Println(err)
	}
    // Sending 200 status code.
	w.WriteHeader(http.StatusOK)
	t.Execute(w, nil)
}

Now it’s time to see our pages in action! Run our server go run main.go, open the page without early hints http://localhost:8082/1 in Chrome, open inspector, go to Lighthouse tab and click on “Analyze page load” button. And this is what we can see: alt text It takes a while until bootstrap resources got loaded by a browser. As result, FCP (First Contentful Paint) is 1492,8ms.

Now, let’s do the same for the page with the early hints http://localhost:8082/2 And this is a result: alt text As you can see, the page loaded much faster now. Bootstrap dependencies (bootstrap.min.css and bootstrap.bundle.min.js) were preloaded in the beginning and FCP now is 437,8ms. More than 3 times faster, quite an impressive result!

However, it does not mean that you have to preload absolutely all resources now. Just try to experiment with these things, see how it affects your page performance and decide for yourself the right balance.

You can find the source code here.

Related posts

Ristretto - the Most Performant Concurrent Cache Library for Go
Ristretto - the Most Performant Concurrent Cache Library for Go
2 Mar 2021

Ristretto - the Most Performant Concurrent Cache Library for Go

Recently, I discovered a surprisingly reliable memory caching solution, which I’m planning to use in all my further applications to increase performance. In this blog post, I will share some code examples of how you can integrate Ristretto caching library into your application.

go caching ristretto performance