How to Build a Free Static Website in 2021
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.
- 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.
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
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.
- 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.
- 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.
- 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.
✨ 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.
- 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. - 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.
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.
- 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. - 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. Thepermalink
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.
- 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.
- Add
gem "jekyll-sitemap"
toGemfile
file in the:jekyll_plugins
section. The runbundle install
. - Add
jekyll-sitemap
to theplugins
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.
- Use
META
. I am using a partial file for theHEAD
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. - 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. - 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.