Blogception v2
I just finished building version two of my personal website and this is the first post since the update, describing the new setup of this website.
tl;dr This website runs on Firebase Hosting deployed as a static site built with GatsbyJS.
Old Setup
My old website was built with Wordpress and hosted on Google Cloud running on Kubernetes. I was not comfortable with continuing to maintain the old setup in terms of costs and time required for managing the website. You can find the post about the old setup here.
New Setup
The new version of my website is built as a static website with no database backend. All the pages are prebuilt and deployed as individual files. This setup makes it easy to deploy and host the website on any hosting provider. As you will see in the section about costs, it is possible to host a static website for free.
GatsbyJS
The are many popular static site generators to choose from. Some of the most popular are:
Jekyll (Ruby), Hugo (Go) ,
Hexio (Node), Pelican (Python) ,
MiddleMan (Ruby) and the more recent one GatsbyJS (Node).
I decided to go with GatsbyJS because the framework has a novel approach to building static websites that can feel like a dynamically built website. The framework is now already in version 2 and at the time of writing this post, the GitHub repository already has over 30k stars.
Features
Gatsby is based on React, the Javascript UI framework from facebook. The statically generated pages still contain react components that enable dynamic client-side rendering after the first initial page load. Gatsby also does link prefetching and navigation without requiring a full page reload.
Check out a comparison and a list of the features on the Gatsby website: https://www.gatsbyjs.org/features/
Data sources
Gatsby supports many different data sources via its plugin system. A data source makes data available to query against and to generate static pages.
The most used data source is markdown. The following markdown is how I add a new post to the blog.
---
templateKey: post
title: "Post Title"
date: "2019-02-17"
image: /assets/blog-setup.png
single: true
draft: false
tags:
- website
---
Post Content
I store these markdown files in a folder "data/post". The structure of the markdown has no pre-defined schema, it makes sense to structure all the files in a folder, in this case "post" folder in a unified way. This data can later be quired against to build static sites. We are free to add many more subfolders, I added one for books that are displayed on the books page.
There are many more data source plugins to load data into Gatsby. There even is a Wordpress source plugin. Just to be clear, the source plugins are used while the static site is being built and not while a user is browsing your site.
GraphQL
Now here comes the part that makes Gatsby special. After data is loaded into Gatsby via the source plugins. We can use GraphQL to query it and create static pages for each entry returned by a query.
The following query returns a list for all markdown files where the templateKey is set to "post".
{
allMarkdownRemark(filter: {frontmatter: {templateKey: {eq: "post"}}}) {
totalCount
edges {
node {
id
html
frontmatter {
title
image
tags
}
}
}
}
}
If we run Gatsby in development mode, there is a GraphQL query editor available in the browser to test and execute queries against the data sources.
Gatsby requires quite some custom coding, but it is very flexible and powerful once you understand its concepts.
React components
As mentioned, Gatsby is built with react components. All the static rendering is done through the components render functions. The data queried via GraphQL can be stored in local state variables on the components. This data is still available on the generated static pages. This allows us to build some dynamic client-side rendering after the page load based on the stored data. I have built the Books page by querying the data from the Markdown files and storing all the books in a local state variable, now when a user searches for a book, I can just loop on the local state and filter the output to include only books that match. The UI is updated automatically by react to reflect the state changes. This gives a very dynamic feel to the website.
Hosting
For hosting we can choose any static hosting file provider with custom domain support. There are many available to choose from, Netlify is a popular choice for free static file hosting.
Since I am a user of Google Cloud I decided to use the Firebase Hosting offering. Firebase Hosting enables static site hosting for free including a free SSL certificate. Firebase Hosting is internally based on Google Cloud Storage and it automatically distributes content to the Google edge caching locations.
Deployment
After having installed the Firebase CLI tools we can create a firebase.json file to configure what parts of your website should get deployed. For Gatsby, a folder "public" is created when the static site is built. The following config makes sure that only this part is deployed.
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}
To read more about the hosting setup you can check out the documentation. After we successfully authenticated with Firebase we can deploy the website with just one command.
> firebase deploy
Costs
With the Firebase Spark Plan I now pay essentially zero for the hosting of this website.
The old pricing is based on google cloud "g1-small" for Compute Engine and "db-f1-micro" for Cloud-SQL.
old | new | |
---|---|---|
database | 15$ | - |
vm | 15$ | - |
network engress | 5$ | 10/gb free |
total | 35$ | 0$ |
Scaling
The old setup was built on Kubernetes with scaling support in mind. But still this required to make use of the Kubernetes Autoscaler and any node added would increase the cost for running the website.
Now with Firebase Hosting, I essentially get scaling for free.
#Continous Deployment Because there is no database, each time any content changes, the site has to be completely rebuilt. This requires having Gatsby installed on the local machine in order to create a new build with the static files and then deploy them to Firebase Hosting.
To create the ability to add a post to the website just by adding markdown files to a Github repository and making a commit, I decided to automate the process fo building and deploying the website using a Continuous Deployment solution.
There again are many CI/CD solutions and services to choose from. Some popular ones are TravisCI, CircleCi, Gitlab CI, Jenkins.
I went the Google Cloud route and used their new service Could Build. The service includes a generous free tier of 120 build-minutes per day which should be more than enough for my use case. Cloud Build is based on simple steps, each executed in a specific docker container that can be defined in a build file. The following cloubuild.yml builds and deploys the website.
steps:
- id: 'install all node packages ( including gatsby )'
name: 'node:10.15.1'
entrypoint: yarn
args: ['install']
- id: 'build gatsby static site'
name: 'node:10.15.1'
entrypoint: yarn
args: ['run','build']
- id: 'deploy to firebase hosting'
name: 'gcr.io/$PROJECT_ID/firebase'
args: ['deploy','--only','hosting','--token','$_FIREBASE_TOKEN']
There are only three steps needed to build and deploy the site. The steps run in a directory where to full source of the project is available. The final build step requires a custom docker image that can be found on the cloud build community repository.
Trigger
Cloud Build allows us to create triggers from Github commits. I created a trigger every time a commit is made to the production branch. The build currently takes about 3 minutes and everyone can see the changes live on this domain.
Closing
GatsbyJS has a steep learning curve, I currently would not use it for critical and long term website projects. Speaking from experience, javascript projects require a lot of maintenance with constant API changes and packages updates. But I'm quite happy with the new setup, in terms of page speed and the time required to manage the website ( essentially zero ) and the auto-deployment pipeline by just pushing to Github. If you have any specific questions or if you want the hear in more detail about the setup do not hesitate to ask me in the comments.