Photo by Halacious on Unsplash

How to Build a Free Static Website in 2021

Stefan Neculai

--

I am writing this article as an open guide for creating a free static website using Jekyll and Github Pages. I hope that will turn in helping others to create better websites. I’m choosing this combination of tech stack because I find it reliable, easy to use, and maintain.

Static presentation websites are better than dynamic ones. They have better security and no complex dependencies. They are fast and you can host them for free (or cheap).

In this guide, I am going step by step from installation to adding more advanced tools that modern web apps. That includes contact forms, subscribe forms, and analytics to track your website traffic. For those that want to dig into details, at each step I provide explanations about why it is necessary. Here is a short summary of what we’re going through:

  • Create and host a Jekyll website on Github Pages
  • Create a build script with NPM
  • Use a custom domain
  • Create custom pages in Jekyll
  • Use SCSS and include the Bootstrap framework
  • Add a sitemap to your application
  • Optimize for SEO
  • Add a website icon

Create Github repo

Github Pages is a cool Github feature that allows you to host your static website for free on Github. Underneath it uses Jekyll to make the website a little bit more dynamic and easier to maintain. To get started with Github Pages, you will need a Github account and git tool installed on your system.

  1. Create a repo. Go to https://github.com/new and create a new repository called my-app. Then you select if you want to keep the repository public or private. As a best practice add a .gitignore file to select the files ignored by git and choose a license for the project.
Create a Github repository

2. Clone the repo. After creating the repo, make a local copy and navigate to the project folder.

git clone git@github.com:YOUR_GITHUB_USERNAME/my-app.git
cd jekyll-app
Copy the URL to clone a Github repo

Add Jekyll

The next step is to add Jekyll to our app. For this, we should have Jekyll installed on our system. More details are available in Jekyll Official Docs.

  1. Init Jekyll app. In this phase, we bootstrap our application by using the Jekyll default build.
jekyll new ./ --force

2. Install bundles. A Jekyll application uses a Ruby environment and it has a Gemfile file where we configure all the project dependencies.

bundle install

3. Run the app. Once the dependencies are installed, we can run the application on our local environment. The build for our application will be available in the _site directory from the application root, which you can check if you are curious how Jekyll puts together the code into a static website.

bundle exec jekyll serve

Now the application is available in the browser on http://localhost:4000/. By default, Jekyll comes with the theme called Minima and it is configured to run a blog on it. Our goal is to use Jekyll for a static website, so we still have to make some changes.

4. Commit changes to Github. We’re hosting our application on Github, so we want to make sure that all the changes are up to date. For that, we will push our changes to Github.

git add .
git commit -m "Our first commit."
git push origin master

Create the build script

By default, we can start a Jekyll project as described above, though that doesn’t come easy when we want to create more advanced builds. I like using NPM and create special rules to build and deploy an application. NPM is widely used and is handy for other developers to understand what is there. It is also good to have later on when we’ll want to add more JS packages to use in our application.

  1. Add NPM. We will be asked for a few things such as project name, project description, license to use, keywords, and author. Using NPM will create a folder called npm_modules where all the files used by NPM are stored. It is best practice not to commit it, thus make sure you add it to .gitignore file before committing any changes.
npm init

2. Add build script. In the script section of the package.json file add the following rule. It works as a wrapper for the jekyll build command and makes our project better organized.

"dev": "jekyll serve"

3. Run the project. Once we’ve added the dev rule, we can run the project by running a more generic NPM rule. The project will become available just like before on http://localhost:4000/. Don’t forget to commit the new changes to Github!

npm run dev

Host website on Github

The great thing about Github is that it comes with Github Pages where we can host our application for free. While can use the master branch for that, it is best practice to create a separate branch calledgh-pages , where we’ll keep out application build.

  1. Install gh-pages package. We will use this package in the next step to create the build of our application.
npm install --save gh-pages

2. Create a deploy script. Just as with the build script, we will add two rules to our package.json file. The first one tells Jekyll to build the application ready for the production environment, while the second one creates the production-ready build.

"predeploy": "JEKYLL_ENV=production jekyll build",
"deploy": "gh-pages -d _site"

3. Deploy our app. This step is creating the application production build and pushing the changes to Github on the gh-page branch.

npm run deploy

4. Activate Github Pages. Once we’ve run the previous command, our production-ready code will be on Github on the gh-pages branch. Github automatically detects it and it enables the Github Pages feature for us. If you go to Settings and then scroll down to the Github Pages section, you will see something similar to the image below.

Enable Github Pages

✨ Now if you access the website, you will see that the CSS style is missing. That is happening because our website is hosted on Github on /my-app path instead of the root path. There is an easy fix for this problem, though if you plan to use a custom domain instead of the Github default host, you can skip step 5.

5. Fix CSS. All the configuration for Jekyll is set via the_config.yml file from the root of our application. Fixing the paths for CSS requires changing the baseurl property for our website. Thus, we just change it from "" to "/my-app" . Next, we deploy new changes and refresh our Github live website. Sometimes, it takes a few minutes until the changes show live, so enjoy a stretch while waiting for them.

Use a custom domain

To add a custom domain to your application, you have to purchase a domain from a domain provider. You could use any domain provider, though I personally prefer DNSimple. Compared to other domain providers, it is focused on providing a great service rather than selling domains at a low cost and then try to sell other things from which they make a profit.

  1. Add CNAME file. Github knows to read the custom domain from a CNAME file which is at the root of your project. Just create a CNAME file and add your domain name inside it. E.g. www.example.com.
  2. Deploy changes. Once you’ve added the CNAME , deploy the changes and you will see the domain name appear in setting. Then, you should be able to see something like in the image below.
Use a custom domain with Github Pages

3. Add a DNS record. Navigate to your DNS provider and create a CNAME record that points your subdomain to the default domain for your site. For example, if you want to use the subdomain www.example.com for your user site, create a CNAME record that points www.example.com to <username>.github.io. More details are available on Github Documentation.

Define website file structure

By default, Jekyll comes as ready-to-use for blogging and it reads the blog posts from the _posts folder. To make my projects easier to browse and maintain, I prefer to keep a separate folder called _pages inside which I put the main pages of my application.

  1. Create a _pages directory in the root of your app. We will store here all the pages that are on the top level of our application. E.g. contact, pricing, features, etc.
  2. In _config.yml register the pages collection. For Jekyll to render correctly, our pages, we have to tell it how we want to access them. The permalink option determines the URL we’ll use for our links. More details about customizing the permalinks are explained in the Jekyll docs.
    👉 Make sure that you restart Jekyll after making these changes!
collections:
pages:
output: true
permalink: /:title

3. Add a default layout. Layouts are an easy way to organize your Jekyll project so that we don’t write repetitive code. A layout file defines the top code structure of a page. Thus, I create a default layout in _layouts/default.html.

<!doctype html>
<html lang="en">
<head>
<title>My Cool Website</title>
</head>
<body>
{% include header.html %}{{ content }}{% include footer.html %}</body>
</html>

4. Update index. By default, you will find a index.markdown file at the root of the application. Rename it to index.html and update the content to the following. It now tells Jekyll to use the default layout that we defined just before, and then to render its content. It will automatically replace {{content}} from the layout file with the contents of our pages.

---
layout: default
---
<h1>Hello World!</h1>

5. Add Header and Footer. With Jekyll, we can define and maintain repetitive code in separate files. They are called partials and you can find them in the _includes folder. We already included them in the default layout by using the {% include header.html %} and {% include footer.html %}. Thus, we just have to create the header.html and footer.html files in the _includes folder.

<!-- This is out header. -->
<header>
Header
</header>
<!-- This is our footer. -->
<footer>
Footer
</footer>

6. Create a new page. To create new pages, all we have to do is to create a new file in the_pages directory. Similar to the index file, we tell what layout we want to use and the content for the page. Here I created a contact-us.html file and which I can access at the http://localhost:4000/my-app/contact-us url.

---
layout: default
---
<!-- Use any HTML here -->
<h1>Contact Page</h1>

Use SCSS and the Bootstrap Framework

Instead of CSS, I prefer using SCSS because it makes it easier to write and customize mine CSS. We’re going to include the Bootstrap Framework which requires SCSS for customization.

  1. Enable SCSS. To enable SCSS, just add the following in you _config.yml file.
    👉 Make sure that you restart Jekyll after making these changes!
sass:
load_paths:
- _sass
- node_modules
style: compressed

2. Create the main CSS file. Add a file called main.scss in assets/css directory. We’ll just define a basic rule for the page background to make sure that it works first. If we did everything correctly, we should be able to see the page with a red background when we refresh it.

---
# Front matter comment to ensure Jekyll properly reads file.
---
body {
background: red;
}

3. Use the CSS file. Once we created the CSS file, we should include it on our pages by adding the following line in the <head> section in _layouts/default.html .

<!-- Styles -->
<link href='{{ "/assets/css/main.css" | prepend: site.baseurl }}' rel="stylesheet">

4. Install Bootstrap. We’ll use NPM to install Bootstrap.

npm install --save bootstrap

5. Include Bootstrap CSS. As we use NPM, Bootstrap will be installed in the nodes_module folder. We already configured our app to include the SCSS files from the nodes_module folder, thus we can just include Bootstrap in our CSS files like this:

// Required Bootstrap files.
// Required
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins";
// Optional files.
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
@import "bootstrap/scss/images";
@import "bootstrap/scss/code";
@import "bootstrap/scss/grid";

6. Include Bootstrap JS. The Bootstrap JS is enabling rich components such as popups or header more advanced navigation. There are a few steps that we should follow in order to achieve that. Mainly, we need to get the JS from the node_modules to be read by our app. For that, I like to have a script that just copies the JS into a vendor folder in my app. Thus:

  • Create a folder called vendor in the assets directory.
  • Install jQuery and Popper.js dependencies.
npm install --save jquery
npm install --save popper.js
  • Create a file called copy_vendor.sh in the root of your app with the following content so that we bring the vendor JS files in our project.
cp ./node_modules/jquery/dist/jquery.min.js assets/vendor/
cp ./node_modules/popper.js/dist/umd/popper.min.js assets/vendor/
cp ./node_modules/bootstrap/dist/js/bootstrap.min.js assets/vendor/
cp ./node_modules/popper.js/dist/umd/popper.min.js.map assets/vendor/
cp ./node_modules/bootstrap/dist/js/bootstrap.min.js.map assets/vendor/
  • Update package.json scripts with the following. These commands, update our build so that it always copies the vendor files before building the project.
"copy-vendor": "bash copy_vendor.sh",
"dev": "npm run copy-vendor && jekyll serve",
"predeploy": "npm run copy-vendor && JEKYLL_ENV=production jekyll build"
  • Include vendor files in _layouts/defaults.html .
<script defer src="{{'/assets/vendor/jquery.min.js' | prepend: site.baseurl}}"></script>
<script defer src="{{'/assets/vendor/popper.min.js' | prepend: site.baseurl}}"></script>
<script defer src="{{'/assets/vendor/bootstrap.min.js' | prepend: site.baseurl}}"></script>
  • Restart your app.

Add Sitemap

Adding a sitemap in Jekyll is super easy and you can do it 2 steps.

  1. Add gem "jekyll-sitemap" to Gemfile file in the :jekyll_plugins section. The run bundle install .
  2. Add jekyll-sitemap to the plugins section of _config.yml. Restart your app and the sitemap is available on http://localhost:4000/my-app/sitemap.xml.

Optimize for SEO & social networks

Optimizing all our pages for SEO is essential if we want to get good organic traffic. Here are a few things to do right away when creating a new website.

  1. Use META. I am using a partial file for the HEAD section of each page which looks in my project for filling all the good meta properties. You can include it by creating a _includes/head.html file similar to this one.
  2. Default texts. The partial from step 1 uses some variables from the _config.yml file. Please make sure that you update: name, title, email, description, twitter_username, social_image.
  3. Custom texts. The title, description, and social_image can be customized for each page, by defining them at the top of the page. Here is an example of doing that for our contact page.
---
layout: default
title: Contact Us
description: We love to hear from you. Just drop us a line.
social_image: custom_image.jpg
---
<!-- Use any HTML here -->
<h1>My Awesome Page</h1>

Add Website Icon

The head.html partial from the previous step already includes the code for the icon. Thus, you should just create the following files in assets/img/favicon . They are optimized to work correctly both on mobile and desktop websites.

  • favicon.ico
  • apple-touch-icon.png
  • apple-touch-icon-72x72.png
  • apple-touch-icon-144x144.png

To make everything easier, a skeleton project with all the setup from above is available on https://github.com/stefanneculai/just-a-website. You can use it as a starting point to create a free presentation website and then host it for free on Github.

If you have any questions or suggestions please let me know, so that I can make the appropriate corrections.

--

--