Skip to content

Creating a static website with Google Cloud’s Storage & App engine

I recently had some experiences with Hugo.

Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.

https://gohugo.io/

I wanted to create a new website for myself and figured I’d give it a go. These are my experiences with it.

General thoughts about Hugo

I like the principle of Hugo. It enables you to create content in a fairly easy way and generate static files for it. Out of the box, it provides various internal functions to make that generation easier. Especially when your theme is set up properly you can and should include “building blocks” to generate your pages. It is possible to write simple content and render it in a specific way so that it becomes a blog or a news page.

Personally, I do feel that I’m working with something that is based on the fact that it generates static content while still wanting that dynamic part. When I looked for templates to use, I noticed that they aren’t really “done”. I spend an awful lot of time “fixing” themes to make them actually work with the functionalities that I wanted. So my 2 cents is that you should really invest in a decent frontend person who can create a theme. Even still, you still will be investing time by tuning various things to make it work for you, aka the “backend” – functionalities.

The tl;dr version is that even though Hugo is made with a specific goal in mind, you are still investing time in the development which might make you wonder why you haven’t picked for something more dynamic in the first place to get such functionalities out of the box.

Creating my website

Perhaps the somewhat negative opinion about Hugo aside, it is still pretty dope. To start with you can just run

hugo new site sysrant

This will setup the basic structure of your website. Now we can incorporate a theme, or create one yourself. Obviously I’m really bad at frontend so I picked one of the internet.

The implementation is pretty easy, you cd into your theme folder and run a git clone of the theme. 99% of the themes are on Github. In our config file the config.toml we will define our theme and basically we are good to go.

Developing locally is really easy too, either by just running hugo server for a local live preview on localhost:1313 or just generate all the public files by running hugo and manually visit your website.

A contact form

I wanted to have a static website, so that part is somewhat covered. Nonetheless, I wanted a contact form. This is dynamic and since I’m just running plain static HTML files I cannot incorporate this straight into Hugo. The easy solution is to create some sort of application which can handle “calls” from my static website and process it.

HTML can POST data to whatever URL you want. That’s easy:

<form id="contact" action="https://sysrant.com" method="POST">

Google App Engine

So thats where the App Engine” comes in play. The short version is that you can simply deploy “something” and it runs. End of story. In my case I just want something to run and not deal with the management of its underlaying system/infrastructure. Why? Because this is not my “core business”. It’s just one simple application. The difference for example this website, is that I have multiple websites and I like to be more in control on what I do with it. Since I can re-use it and it gives me more features, I can justify the costs to invest in managing it myself.

For language wise

Build your application in Node.js, Java, Ruby, C#, Go, Python, or PHP—or bring your own language runtime

https://cloud.google.com/appengine/

In which I just picked Go. Again the why? Because I love it. I believe it’s a mature language which is really powerful in its functionalities. I’m a fairly good PHP programmer but I don’t really think PHP is the right language for a simple serverless app. I don’ believe that that is its strength. If I can choose between Python and Go I will always go for Go (pun intended). The reason is that I don’t work in the field where Python can shine. So if we are talking about a serverless app that functions as an API and has to do “some” stuff; Go. Honestly, there is absolutely nothing wrong with Python but it is my personal preference.

What’s left is Java, Ruby and C#. I personally have less experiences with those languages but I do feel that those are an overkill on what I want to achieve.

I kept NodeJS for last; since yea.. Javascript. I really don’t want to touch that. I do understand the power of NodeJS but I really, really do not see the use of it if you can use one of the languages in the list for a serverless app.

Creating the contact “app”

runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app

I started with a simple app.yaml this tells Google what and how I want to run something. Basically I’m telling here that I want to parse all the URL’s and it runs in Go.

Next is my “app” itself in Go. I started with the main function to parse the URL’s and invoke the appengine itself.

func main() {
	http.HandleFunc("/", parser)
	appengine.Main()
}

In the parser we have access to the request and we can write a response back

func parser(w http.ResponseWriter, r *http.Request) 

Since I want to POST to this app I had to set these headers:

(*w).Header().Set("Access-Control-Allow-Origin", "*")

Next I just use r.Method to check if it’s an POST request. If so I want to parse my form with r.ParseForm(). Now I can access my data (if there are no errors) with r.FormValue(“whatever-input-you-have”)

One thing to keep in mind is that not all functionalities are allowed in the App Engine. http.DefaultTransport and http.DefaultClient is not available in App Engine. So this sort of sucks for two reasons. 1: You have to alter your scripts that used to work. 2: If you use another library who uses those calls, you must override them.. that is if such library supports it. Fun fact: Not every package exposes those functions.

That will result in the need to fork it and change their internals in order for your application to work. In all fairness a good package does expose those methods.

Anyway, you can use: “google.golang.org/appengine/urlfetch” for those functionality.

client := urlfetch.Client(ctx)

When I was done I simply ran gcloud app deploy from the CLI and my application was deployed. I’ve got a URL on where it runs and I can simply use that URL as my POST value in my HTML form.

Deploying Hugo to Google Cloud

I first created a bucket:

gsutil mb gs://isitthough.com

In order for this to work, you must have your DNS pointed with a CNAME towards c.storage.googleapis.com. That is, if you want to use a domain name and host a website.

After that I’m telling the “bucket” to serve the index.html as default and use 404.html for… the 404 page.

gsutil web set -m index.html -e 404.html gs://isitthough.com

As default I’m also telling my bucket to serve my files to any user. By default it’s not public.

gsutil defacl ch -u allUsers:READER gs://isitthough.com

And the last thing I have to do is sync my files to my bucket. I just synced my entire “public” folder from Hugo:

 gsutil -m rsync -R public gs://isitthough.com

There is just one catch. By default, Google will cache public files. So if you allow anonymous users to access any file, they will have a cache header. This really sucks. It means any changes you will make will not be visible and you cannot clear/purge this cache. You will have to wait until the max-age reaches for that file.

We can solve this by running a command to set our own headers:

gsutil setmeta -h "Content-Type:text/html" \\n  -h "Cache-Control:public, max-age=3600" \\n  -h "Content-Disposition" gs://isitthough.com/*.html

It’s vital to make use of this. I can recommend to set the caching to 0 and use a different type of cache later on (for instance CloudFlare, more on this later).

Please keep in mind that you must run the setmeta command every time you alter your file since it’s a command that gets set on a file. I recommend making a decent deployment script which incorporates the correct headers.

Finishing touches

I finish my project by placing CloudFlare in front of my website. This solves a few “issues”. First of all you can have a free SSL from CloudFlare which is great. Second is that CloudFlare actually allows setting a CNAME record on your root domain. Most providers do not allow such thing, only for sub-domains. The last part which is obviously handy is that it can prevent malicious behaviour. This is sort of vital when you are server your website with a “pay per use” policy.

The costs are still cheap but if you are under attack for weeks, you will notice it in your pocket eventually.

The same goes for my App, I implemented Recaptcha v3 on my website. This prevents executing all the follow-up actions (such as Posting to Slack) if it’s spam.

Conclusion

I start with CloudFlare for SSL, protection, cache and it’s CNAME root capabilities. It will serve files from a bucket which required different meta headers to disable cache. The static website itself uses an API hosted on Google’s App Engine which has a simple Go “script” to process the data. In my case, it will get posted in Slack. To prevent spam it uses recaptcha v3 which removes bothering people with “questions”. This is a huge improvement on recaptcha v2.

If we are looking into costs, it’s a steal. I only pay for when my App Engine runs and it’s really fast in doing what it does. The price itself is $0.05 / Hour and I have tested it quite a lot when developing. This resulted in a quota of 0.09 Instance Hours, which cost me 0$…

The bucket has some freebies too. You get a free regional Storage of 5 GB per month and 5000 class A and 50.000 class B requests. With that in mind, we also cache things via CloudFlare. Honestly, I doubt I’ll ever have to pay for it.

Not forgetting the fact that the ease of “development” and hosting is a big win, I’m really happy with this setup.

Published incloud nativeGoogle Cloud

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Enjoy life!