There are several inexpensive ways to host a static website generated with a static site generator like Jekyll, Hugo, or Pelican:
This entire blog is statically generated using Jekyll. However, I am unable to use any of the options above, because, over this blog’s lifetime, I have changed domain names, changed URL schemes, and renamed posts, and I want to keep alive all of the old URLs

I have been hosting this blog using Apache and, more recently, nginx on a single virtual machine, and the redirection features of either piece of software work just fine, but I was ready to host it somewhere new and different

A previous post describes how I redirect URLs from an old domain to a new domain using Google App Engine and Python, but now I needed a way to serve static content
**and** redirect URLs from the same domain. That same domain redirection requirement is why I cannot simply use Google App Engine’s static content only feature (linked in the list above). However, I can use Google App Engine in combination with a simple Golang application to serve both static content **and** same domain redirects

## Why Google App Engine?
Before you dive into the rest of the post, perhaps you are wondering, why host a blog on Google App Engine? Here are my reasons why:
- If your traffic fits within App Engine’s free tier of 28 instance hours and 1 GB of egress traffic per day, hosting the blog is practically free
- Pushing updates is done with one command
- Logging and monitoring are integrated using Stackdriver
- Automatic up and down scaling based on traffic patterns
- With a few clicks, web logs can easily be pushed to something like BigQuery for long term storage and ad hoc analysis
- Managed SSL certificates using Let’s Encrypt
## Prerequisites
This post assumes the following:
- You are familiar with Google Cloud Platform (GCP) and have already created a GCP Project
- You have installed the Google Cloud SDK
- You have authenticated the
gcloudcommand against your Google Account
 Create a GCP Project
If you have not yet created a
**GCP Project follow these steps:
- Open a web browser, and create or log in to a Google Account
- Navigate to the GCP Console
- If this is your first GCP Project, you will be prompted to create a GCP Project. Each Google Account gets $300 in credit to use within 12 months towards GCP. You are required to enter a credit card to create a GCP Project, but it will not be charged until the $300 credit is consumed or 12 months expire

- If this is a new GCP Project, you will need to enable the Compute Engine API by navigating to the Compute Engine section of the GCP Console and wait for initialization to complete
 Install the Google Cloud SDK
If you have not yet installed the
**Google Cloud SDK follow the instructions here

 Authenticate gcloud
Once you have created a GCP Project and installed the Google Cloud SDK, the last step is to authenticate the
gcloud command to your Google Account. Open your terminal application and run the following command:
gcloud auth login
A web page will open in your web browser. Select your Google Account and give it permission to access GCP. Once completed, you will be authenticated and ready to move forward

## Create a Directory
Next, create a directory somewhere on your workstation to store your Google App Engine application:
mkdir ~/Sites/example.com/app_engine
Change into that directory:
cd ~/Sites/example.com/app_engine
The remainder of this post will assume you are working inside of this directory

Additionally, create a directory inside of the
**app_engine** directory called **static
mkdir ~/Sites/example.com/app_engine/static
You will revisit this directory later

## Create app.yaml
Google App Engine typically requires two files:
**app.yaml** and an **application file** written in Python, Golang, Java, or PHP - in this case it’s going to be Golang. **app.yaml** provides the necessary configuration to run your application. There are a lot of different parameters that can exist in **app.yaml Those parameters might differ based on the programming language used. For this post, Golang will be used, and you can find all the available Golang parameters here

Create file
**app.yaml** with the following contents:
runtime: go api_version: go1 handlers: - url:script: _go_app secure: always redirect_http_response_code: 301
Notice that
**secure: always** has been set. This means the Golang application will always be served over HTTPS. If an end user navigates to the web application over HTTP, they will by default be 302 redirected to the HTTPS version. This is why **redirect_http_response_code: 301** has also been set. I always want the web application to be served over HTTPS, and I do not want search engines interpreting the redirection from HTTP to HTTPS as a temporary redirect; it is a permanent redirect

If you have static assets, and you probably do, it is best practice to inform App Engine of this and let it serve those assets from object storage instead of from your application. Doing this is easy and is also done through the
**app.yaml** file

For example, if you have a favicon file, a CSS directory, a Javascript directory, and an images directory, use the following
**app.yaml** file:
runtime: go api_version: go1 handlers: - url: /favicon.png$ static_files: static/favicon.png upload: static/favicon.png - url: /css static_dir: static/css - url: /js static_dir: static/js - url: /images static_dir: static/images - url:script: _go_app secure: always redirect_http_response_code: 301
## Create main.go
Next, you need the Golang application file

For the following code to meet your needs, create file
**main.go copy and paste the code below, and make the following modifications:
- In the
domainvariable, change the value to match your domain name with the correct HTTP protocol

- In the
urlsmap, replace all of the key value pairs to match the redirects you need in place. Replace each keywith just the path portion ( /example-post-1.htmlinstead of httpsexample.com/example-post-1.html) of the current domain’s old URL you want to keep alive. Then replace each valuewith the path portion of current domain’s new URL you want to redirect to

All redirects will be 301 redirects. This can be modified by changing
**301** in the code below to a different HTTP redirect status code such as **302**

package main import ( "net/http" "os" "strings" ) func init() { http.HandleFunc handler) } func handler(w http.ResponseWriter, r *http.Request) { // True (ok) if request path is in the urls map if value, ok := urls[r.URL.Path]; ok { value = domain + value http.Redirect(w, r, value, 301) } else { path := "static/" + r.URL.Path // Return 403 if HTTP request is to a directory that exists and does not contain an index.html file if f, err := os.Stat(path); err == nil && f.IsDir() { index := strings.TrimSuffix(path,+ "/index.html" if _, err := os.Open(index); err != nil { w.WriteHeader(403) w.Writebytehtml>403 Forbidden

403 ForbiddenReturn custom 404 page if HTTP request is to a non-existent file if _, err := os.Stat(path); os.IsNotExist(err) { w.WriteHeader(404) http.ServeFile(w, r, "static/404.html") return // Withoutreturn, a "404 page not found" string will be displayed at the bottom of your custom 404 page } http.ServeFile(w, r, path) return } } var domain string = "httpsexample.com" var urls = map[string]string{ "/example-post-1.html": "/post/example-post-1.html", "/example-post-2.html": "/post/example-post-2.html", "/example-post-3.html": "/post/example-post-3.html", } ## Generate the Static Content With **app.yaml** and **main.go** saved, the last piece is to generate your static content and store it in the **static** directory you created earlier How you do this entirely depends on what static site generator you are using If you are using Jekyll, you can configure the **destination** parameter in Jekyll’s _ **config.yml** file to save your static content in any directory on your workstation. So, you could set the **destination** parameter to Sites/example.com/app_engine/static and, every time you run jekyll build, the static content will be saved in that directory ## Deploy to App Engine With **app.yaml **main.go and your static content generated, you are ready to deploy your Google App Engine application Assuming gcloud is already pointed at the Google Cloud Project you want to deploy to, verify with gcloud config list project, run the following command: gcloud app deploy The command will output the appspot URL your application will be deployed to and ask if you want to continue. Typically, the appspot URL is **httpsyour-project-id.appspot.com This is also a useful self-check to make sure you are not deploying your application to the wrong Google Cloud Project. If everything looks okay, type **Y** and **Enter** to deploy your application. Depending on how large your static content is, your application should be deployed within about one minute ## Setup DNS At this point, your application is deployed under URL **httpsyour-project-id.appspot.com Unless your website uses that as its domain name, you will probably want to setup a custom domain that uses your actual current domain name The App Engine section of the Google Cloud Console can be used to do this. Go here and follow the instructions to configure your custom domain Once that is complete and DNS has had time to propagate, you should be able to navigate in your web browser to one of your current domain’s old URLs, for example **httpsexample.com/example-post-1.html and have it redirect to your current domain’s new URLs, for example **httpsexample.com/post/example-post-1.html** ## Pushing Updates To push updates, make the necessary changes in your static site’s source directory, regenerate the static content, and redeploy to Google App Engine by changing into the Sites/example.com/app_engine** directory and running gcloud app deploy ## References - A Surprising Feature of Golang that Colored Me Impressed - How to check if a map contains a key in go? - Disable directory listing with http.FileServer - 3 Ways to Disable http.FileServer Directory Listings - Handling HTTP Request Errors in GO - HTTP and Error management in Go - please add ability to set custom