This page looks best with JavaScript enabled

3. Blog with Hugo on GAE

 ·  ☕ 2 min read  ·  🐨 Puliyo

We’ve covered how to create website in chapter 1,

and how to deploy to GAE in chapter 2,

This chapter describes how to optimise cost of the blog created.

Saving Cost

Basic principle follows the one described in this article.

It is recommended to limit the spending unless if you want to maintain 24/7 uptime of server to prevent spontaneous hike in billing.

Your app.yaml should also contain elements to control instance so you don’t use excessive amount of resources:

automatic_scaling:
  max_instances: 1
  max_idle_instances: 1
  min_pending_latency: "3000ms"

Another important strategy to incorporate is cache control.

Cache for static content such as css/js/img/fonts can be defined easily with expiration and default_expiration element:

default_expiration: "1h"

handlers:
- url: /css
  static_dir: public/css
  expiration: "7d"

- url: /js
  static_dir: public/js
  expiration: "7d"

- url: /img
  static_dir: public/img
  expiration: "7d"

- url: /fonts
  static_dir: public/fonts
  expiration: "7d"

The expiration and default_expiration is only effective to static or static_dir element.

We are not able to attach expiration element to script handler.

- url: /.*
  script: _go_app

This means, we have to define cache control ourselves in main.go.

This website teaches well in how to add HTTP caching in GO.

Putting it into practice, I came up with below main.go

package main

import (
    "os"
	"net/http"
    "strings"
)

func init() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        etag := `"` + "<some etag string>" + `"`
        w.Header().Set("Etag", etag)
        w.Header().Set("Cache-Control", "max-age=3600")
        if match := r.Header.Get("If-None-Match"); match != "" {
            if strings.Contains(match, etag) {
                w.WriteHeader(http.StatusNotModified)
                return
            }
        }
        if _, err := os.Stat("./public/" + r.URL.Path); os.IsNotExist(err) {
            http.ServeFile(w, r, "./public/404.html")
        } else {
            http.ServeFile(w, r, "./public/" + r.URL.Path)
        }
    })
}

When using above main.go in GAE, we have to make sure the etag string (<some etag string>) gets updated whenever serving new version otherwise user will constantly re-use their locally stored cache file.

For convenience, I have created a script file to output new main.go which updates eTag string before deploying to GAE.

Further to add, noticed this line?

if _, err := os.Stat("./public/" + r.URL.Path); os.IsNotExist(err)

By having this check, you can provide user with your own custom 404 page for missing path.

That’s it!

Share on