Static Website Development

I love static websites. And that love has only grown over time. I started my career building dynamic websites with database backends and those are great, but they require a lot more mental energy to build and secure. Static website, on the other hand, require very little effort and are very resilient to being attacked. So this article explains how I build static websites these days. It may not be what you use and that's fine, we can both celebrate our differences.


The first thing I'll mention is that when building static websites you have to remember that you need to accept the fact you can't do some fancy things you used to do with dynamic or server-side rendered websites. But, there aren't many of these drawbacks. The benefits are that you only need a few tools to get you started. This will save your mental energy for other things, like building, designing and maintaining a website. These requirements are as follows:

  1. Web server
  2. Text Editor
  3. Working knowledge of HTML, SASS, and VueJS
  4. Git version control

Web Server

When you build a static website, you could just open the HTML file in your Web browser, but this has two major drawbacks. The first is that you are viewing the file directly (via file://) and, as a result, the security sandbox of your Web browser won't allow certain JavaScript operations to run. The second is that relative and absolute links get mangled and probably won't run properly. So we're going to need to run this in a Web server.

You could download, install, and configure something like Apache HTTPd or Nginx, but that's a lot of muscle for just rendering a static website to your local Web browser. And both maintaining the updates and eventually removing it from your system is going to require a different set of skills that you ultimately didn't sign up for.

Enter Go. With a simple syntax and versatile set of built-in libraries, you can create a Web server instantly. The binary that is created is just one file with all its dependencies built-in, so you can copy it around wherever you want and it will still work (assuming it's all on the same type of operating system). Here's all the code you will need:

package main

import (

func main() {
  fs := http.FileServer(http.Dir("src"))
  http.Handle("/", fs)

  http.ListenAndServe(":3000", nil)

This can be saved into a file called server.go and then built into an executable binary with go build server.go. Obviously you'll need to have Go installed, but that's all you need to do to build a working Web server.

Once the Web server binary is built, you run it with ./server and then open your browser to http://localhost:3000. Any files that reside in the src subdirectory where you put the binary will be viewable in your Web browser. Now, realize that this web server is very basic, it only knows how to serve Web files, so don't expect more advanced things like logging, security, or rate limiting. It is specifically for building static websites locally on your machine.

Text Editor

You can use anything you want so long as it does not add special characters to your output, which means things like Word Processors (MS Word, Google Docs) are out. I use Vi, but this is more advanced and… spartan… than most people like to use, so make sure that you use whatever you feel comfortable with.

If you are starting fresh, here are some good alternatives to get you started:

Working Knowledge of HTML, SASS, and VueJS

Now, when I say "working knowledge" I don't mean you need to be fluent in these languages. Rather, you can know just enough about them to get by and make something. The rest of the learning will come in time and practice. As my website's motto says: Just keep learning.


HTML is very easy to learn, and there are a tonne of resources out there to help you learn just the basics. You really only need the basics to build something beautiful, and then resist the temptation to go to far with more advanced HTML functionality. These HTML files are simple and contain the crux of your static website. I like to build static websites with pretty URLs; that is, my pages end up looking like:

  • and so on…

The way this is accomplished is by creating pages like src/index.html, src/about/index.html, and src/profile/index.html. That way, visitors to your website don't need to enter the .html extension in the URL, but instead just say about. Things stay pretty and your files can be organized into logical units.

The HTML code itself is fairly straight-forward, with the usual <head> and <body> tags that you see everywhere. The links to your CSS and JavaScript files will use absolute paths, such as /assets/css/site.css and /assets/js/site.js respectively.

To prevent duplicating the same headers, footers, and other common widgets in your website's UI, you use VueJS. That will be discussed in the VueJS section later.


SASS is a language built on top of CSS, and it allows for a more friendly, more programming-like experience than when you use plain CSS. But SASS cannot be rendered by Web browsers yet, so it needs to be translated into CSS through the sass tool, which you can download to your computer. SASS gives you the ability to nest CSS rules to reduce duplication, as well as use variables to reduce hard-coding of values.

Another benefit of using SASS is that you can split a SASS file into many parts, and then during the translation step SASS will stitch everything together into a single artifact. We use this to reduce the number of files produced without a large mental load on you, the builder. No, we're not going to NPM levels of abstraction, but SASS gives enough benefits here over plain CSS that it is worth downloading the tool.

I like to make sure that my SASS file resides in src/site.scss and renders to src/assets/css/site.css. This is accomplished by running the command sass src/site.scss:src/assets/css/site.css. Once the Web server is running, it is available in the Web browser at http://localhost:3000/assets/css/site.css.


VueJS is packaged locally instead of using a CDN so that we can switch between using a development version and a production version. The development version will help when building the website because it offers up helpful troubleshooting and error messages in the Web browser's developer console.

Development version is stored in src/assets/js as vue.js. Production version is stored in resources/ as

I like to instantiate VueJS code with three calls in each HTML page: the first for the VueJS library, the second for common VueJS components, and the third for the page-specific components. It looks like this in every HTML page(this example is for my /index.html main page):

    ... html code ...

    <script src="/assets/js/vue.js"></script>
    <script src="/assets/js/common.vue.js"></script>
    <script src="/assets/js/index.vue.js"></script>

VueJS is used to provide some dynamic nature to the application, like common headers, footers, or widgets. On traditional server-side rendering, this would be handled by the dynamic view language (ERb, ColdFusion, JSP, etc.) to stitch everything together before sending it out to the user's browser. With VueJS this all happens at runtime on the user's local browser.

You can create a common header shared by all pages on your website, regardless of which directory they are in, by putting the following code in your HTML page:

  ... some code here ...
  ... some code here ...

and then in your src/assets/js/common.vue.js file you have this:

Vue.component('header-section', {
  template: `
      ... your awesome code here ...

Once the page is rendered in your Web browser, the <header-section> tag is automatically replaced (within milliseconds) by VueJS, without your visitor realizing what is happening. And notice that the VueJS code is just plain JavaScript, nothing fancy and easy to learn just enough to get by.

One thing you'll notice is that I'm not requiring you to use things like NPM or Webpack or whatever the framework du jour is this week. These tools have their purpose, but not when we're building static websites with light JavaScript interactivity. While you may lose out on fancy JavaScript libraries, the side benefit of this approach is that you can completely understand both the tooling you are using and the Web code you are producing.


Git is used to ensure that any changes to your website files are tracked and stored in a permanent manner, locally on your machine or pushed up to a central repository service like Github or Gitlab. It also helps ensure that any mistakes can be reverted by going back into Git's change history and grabbing the correct working copy. It's a great tool that has a bit of a steep learning curve, but well worth it once you learn the basics.

Wrapping it all up

For me, my static websites reside in Amazon Web Services (AWS). That means they sit on a Content Delivery Network (CDN) service called CloudFront, which then copies my website to hundreds of web servers located around the world, resulting in an extremely fast viewing experience anywhere in the world.

Remember that CDNs are just copies – technically, the points of prescence (POPs) contain the copies – so updating your website requires these copies to be flushed from each cache to make way for the new version, so a cache flush is needed.

All of this can be wrapped up in a Makefile so that you don't need to remember archaic CLI commands. My Makefile looks like this: := YOUR_CLOUDFRONT_DISTRIBUTION_ID_HERE
aws.profile := YOUR_AWS_PROFILE_HERE

.PHONY: help
help: ## displays this message
  @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: watch
watch: ## watches for SCSS changes and renders it out to CSS
  sass --watch --sourcemap=none src/site.scss:src/assets/css/site.css

.PHONY: build-server
build-server: ## builds the local Web server into an executable app
  go build server.go

.PHONY: server
server: ## runs the local Web server

.PHONY: deploy
deploy: ## pushes the website up to AWS
  sass --sourcemap=none src/site.scss:src/assets/css/site.css
  cp resources/ src/assets/js/vue.js
  aws s3 sync src/ s3://$(s3.bucket)/ --delete --profile $(aws.profile) --exclude site.scss
  aws cloudfront create-invalidation --distribution-id $( --profile $(aws.profile) --paths '/*'
  git checkout -- src/assets/js/vue.js

I'll briefly explain each target:

  • help: don't try to understand the underlying command. It's a crazy one-liner that I picked up years ago from a website I no longer remember. But what it does is take every target where the comment starts with ## and prints it to the screen. Thus, when you type make in your terminal, it will display all the available targets with an explanation of their purpose.
  • watch: this target ensures that any changes to src/site.scss are automatically transformed into CSS so that the Web server can read it. "Watch" just means that it is watching for changes to the file and reacts accordingly.
  • build-server: builds the local Web server into an executable app for your local machine. This is useful if you work across multiple operating systems.
  • server: this target launches the Web server and starts serving files from src so that you can open your browser to http://localhost:3000 to view your beautiful static website.
  • deploy: Pushes the contents of the website up to AWS and then flushes the CDN cache. It does some movement of the VueJS file so that the production version is launched, then reverts the change after the cache has been flushed so that nothing was changed.

This is too much work! Just get me going, Scott.

I hear you. I have created a public Github repository that contains the basic skeleton to get you going. But do try to understand each of the moving parts in the repository, as it helps you build and maintain these static websites in the long run.