Making a Personal Site from Start to Publish with Eleventy and Github
Tags: github, webdev, eleventy, hosting
Published: 2021-01-12 03:07:57

I recently started the very beginnings of a personal site.  While I've had a lot of experience deploying large corporate websites with big proprietary deploys I didn't really have much experience dealing with all the consumer-side aspects of deployment and devops for people just setting up personal stuff.  So here I'm going to try to document the start to finish process of getting a simple public website up and running. I've done no design or put too much thought into how the site is setup, so this will mostly be about process and tools.  Note that these are all free services sans the optional domain name.

# Getting an Static Site Generator

There's a lot of ways to build a site but for simple, mostly non-interactive sites, or for the types of application I like to make that have no server-side requirements a static site generator is the right tool.  With this we get an output of static HTML, CSS and JS that can be deployed anywhere.  You might also this referred to as JAM Stack.  It's a fancy term but really just means a site compiled at build time and potentially augmented with ajax calls for data.  The anywhere deployment is important because this would get much more complicated if I needed to use a typical business-class cloud service.  I deal with those enough to know I don't want to deal (or pay) for them.

As the title suggests I'm going with [Eleventy](https://www.11ty.dev/).  This was chosen for a few reasons.  I've heard good things about it and wanted to kick the wheels, it doesn't require a big heaping of webpack or react, in fact it comes with a lot of different types of templating out of the box and from the few proof-of-concepts I've done it was pretty simple.  If you are more of a React-centric dev and want to do everything in React I also recommend [NextJS](https://nextjs.org/) which I've also found pretty approachable especially versus the other big player in the space, [Gatsby](https://www.gatsbyjs.com/).

Not knowing exactly what templating I wanted to start with, I just picked whatever the tutorial gave me which happened to be [liquid](https://shopify.github.io/liquid/) which is a templating language developed by Shopify.  What's interesting is that eleventy let's you mix and match, so the posts will be markdown but the index page with the listings is `.liquid`.

Anyway, to use Eleventy you'll need node and npm.  Start a new npm project `npm init` and then run:

`npm @11ty/eleventy --save-dev`

Eleventy has only one real command, `eleventy` to build the site (`npx eleventy` if you didn't global install, you should add it to the "scripts" in your `package.json`).  You can also use `eleventy --serve` to start a dev server with auto reloading.

# Building the Site

The site itself is just static HTML and CSS and not even very pretty.  That's not important right now.  What I want is a collection of blog posts, which will be authored as liquid templates.

First was just creating an `index.liquid` with some "Hello World" text and getting it to render using the commands above (the content isn't important just that the process works).  It should render out an `index.html` to the `_site` directory which is the default output directory.

Once we've done that it's time to add some posts.  I did this by creating a folder called `posts` and adding a couple markdown files with post content.  The content is whatever you want, but the important part is that you annotate it with metadata so Eleventy knows what to do with it.

```md
---
pageTitle: First Post
---
Your content goes here

```
And
```md
---
pageTitle: Second Post
---
Your content goes here

```
This is markdown with some liquid meta data which goes between the `---` fences.  Each piece of data needs a colon after it.

It's also possible to quick add default metadata to a group of files.  I did this by adding a `.json` file called `posts.json` to the `posts` folder.

```json
{
	"layout": "layout-post.liquid",
	"tags": ["posts"]
}
```
This sets two piece of metadata on all of the files in the folder, `layout` and `tags`.  These are special values to eleventy, `layout` tells it which layout template it should use to render the post, and `tags` sets a sort of category for the content and you can add as many as you'd like.

Next we need to make the post template we referred to:

```liquid
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>{{ pageTitle}}</title>
		<link rel="stylesheet" href="/css/system.css">
    </head>
    <body>
        <main class="text-content">
            <h1>{{ pageTitle }}</h1>
            {{ content }}
        <main>
        <a href="/">Back to Home</a>
    </body>
</html>
```
Nothing special here, just HTML boilerplate.  We're going to template in a few values.  You can see the `pageTitle` we set earlier as well as the `content` which is a special value referring to the output.  There's more of these default values like `date` that refer to the file create date, you can find those in the documentation: https://www.11ty.dev/docs/data-eleventy-supplied/.

At this point running `eleventy` will create an html page for each post and it should use both the post content and inject it into the template.

Next, you might have noticed I added some CSS references in the layout.  CSS was a bit more round-about than I expected.  You can't just add some static CSS and expect it to be exported, you need to make it a template too.  So we have our `system.css` file (this is just a custom CSS reset, the file name is unimportant) in the folder `css`.  We need to rename it `system.liquid` and add our liquid metadata:

```
---
permalink: /css/system.css
eleventyExcludeFromCollections: true
---
html {
  background: mageta;
}
```
The `permalink` attribute tells eleventy to always put the content at that url (this is what actually publishes the CSS).  We can also add `eleventyExcludeFromCollections` to state that this is static and not part of the collections system.

Now the post pages should render.

Finally we can create the post list in `index.liquid`:

```liquid
---
layout: layout-home.liquid
pageTitle: "Ndesmic Blog"
---
<h2>Posts</h2>
<ul>
{% for post in collections.posts reversed %}
	<li>
		<a href="{{ post.url }}">{{ post.data.pageTitle }}</a>
		<em>{{ post.date | date: "%Y-%m-%d" }}</em>
	</li>
{% endfor %}
</ul>
```

The `layout-home.liquid` is the same template as used for posts minus the back link.  There's probably a better way to do that.  More importantly is the `collections.posts` for loop.  `collections.posts` is our collection of posts.  The name comes from the folder I believe.  Eleventy supplies the `post.url` and `post.date` properties, and `post.data.pageTitle` is the one we added with the liquid tags.  You can also see a "filter" going on for the date.  That's a liquid thing that lets you format some data, the data functionality is built-in: https://shopify.github.io/liquid/filters/date/.  There's also the `reversed` keyword in the for loop condition which causes it to be iterated in reverse order as they are ordered by date, we want the newest to be first.

And that's it.  A very, very simple Eleventy blog with markdown, liquid, templating and a post listing.  Not too hard at all.

# Building

Next we need to build it.  Locally this works but we don't really want to build it ourselves every time, just when we add some new stuff.  To do this we'll use [Github Actions](https://docs.github.com/en/free-pro-team@latest/actions).

Github Actions are neat mostly because they are free and you can do quite a bit with them.  What we want to do is do an eleventy build when we push some changes to the `main` or `master` branch.

To do this, we need to add a new folder in the git root with data that Github looks at called `.github`.  In there we can add another folder called `workflows`.  Finally we'll make a `.yaml` (shudder) file with the instructions for our build process called `build-eleventy.yaml`.

```yaml
name: Eleventy Build
on: 
  push:
    paths-ignore:
      - README.md

jobs:
  build-deploy:
    name: Eleventy Build
    runs-on: ubuntu-latest
    steps:
      - name: Git Checkout
        uses: actions/checkout@v2
      - name: Build
        uses: TartanLlama/actions-eleventy@v1.1
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          publish_dir: ./_site 
          github_token: ${{ secrets.GITHUB_TOKEN }}
```
First we give it a name which will show up in the actions dashboard.  Next the `on` is the condition when to run, we do this on push but I've set it to ignore the readme because I always change those and don't want to re-run the build every time I touch it because it's not part of the publish.

Next, we have the jobs.  We only have one.  The key `build-deploy` I don't think is actually important but seems to be a convention.  The name however, is the name displayed when we go looking for the build progress/errors.  `runs-on` is container image, ubuntu-latest seems fine though you can customize it.

Now for the meat, the steps in the build job.  First we need to checkout the source code in the container.  Github provides a script that does that for you `actions/checkout@v2`.  Once the code is checked out we need to install and run eleventy.  Luckily, there 3rd party action that can do this for us [TartanLlama/actions-eleventy@v1.1](https://github.com/peaceiris/actions-gh-pages).  This gets us into the same state that we were in after a local build.  Lastly we want to publish to Github pages.  There's another action to do that [peaceiris/actions-gh-pages@v3](https://github.com/peaceiris/actions-gh-pages).  What this does is take the `_site` folder and makes a commit to the branch `gh-pages` with the contents.

Lastly, we tell Github that we intend to serve our pages content from the branch gh-pages.  So provided that branch exists (make it if not) you can select it in the dropdown.

![Screenshot 2020-12-27 155756](https://dev-to-uploads.s3.amazonaws.com/i/3ild9pqw72610qb5tft2.png)

Commit to `main` or `master` and it should start a build and publish.

If you have problems be sure to check the repo pages for those actions as they might get new versions as this post ages or you may need to look for new ones.

Now you should be able to view your site on the appropriate URL:

`{username}.github.com/{repoName}` (add the subdomain between "{username}" and ".github" if you are using an enterprise instance).  If you named the repo `{username}.github.io` then it becomes your profile repo `{username}.github.io`

And there we go.  Site published and from here on out it's automatic.  You just need to make commits to master/main and it'll update automatically.  Do your temporary work and testing on a different branch.

If you don't want to pay for anything you can stop here.

# Getting a Domain

At this point we have a public site, yay!  But to add that extra bit of professionalism we need a custom domain name.  Unfortunately, this part costs money.

One of the places I've had good luck with is [Namecheap](https://www.namecheap.com/). Just find something you like and you pay some amount per year to keep it.  Note that cooler top-level domains (TLDs) `.book`, `.ai` etc tend to cost a lot more than basic `.com` or `.net`.  But if you aren't too picky you can find something for ~$5 a year and they occasionally have sales (I bought mine on Black Friday).

Anyway once you get one, let's talk about what that domain name record means:

## CNAME vs A Records

A name records are a mapping of domain name (specifically fully qualified domain name or "FQDN") to an IP Address.  Basically what you typically associate with a DNS lookup and therefore this is the most common type of DNS record.  The other common type is a CNAME which allows one domain to resolve to another domain. The DNS will query the first and keep following the chain until it gets an IP address.  This is what we want because we're trying to associate a subdomain, in my case `gh.ndesmic.com` with `ndesmic.github.io` (you'll probably use `www.{domainName}` but I'm going to be doing some tests with subdomains).

There's a couple other types of records for doing some special stuff but they aren't necessary here.

---

So if you do choose to go with NameCheap, go to Account > Domain List.  Here you will see a list of your domain names.  Find the domain you want and click "Manage".  While you might be tempted to use the "domain redirect" section to setup a redirect, that will cause an HTTP redirect (code 3XX) which is undesirable.  What we want is a CNAME record.  Go to "Advanced DNS" and then under "Host Records" add a new CNAME record.  Then associate the subdomain with the github domain (do not add the protocol, `http` etc).

![Screenshot 2020-12-27 222422](https://dev-to-uploads.s3.amazonaws.com/i/zvrlukpbxhj9w8qeqz7o.png)

Here's what mine looked like to make `gh.ndesmic.com` go to `ndesmic.github.io`.  Keep the TTL automatic so it shows up faster.  You might need to wait a while (up to 24 hours) for a record change to take effect, though I find it only take a couple minutes usually, it depends where you are and what DNS you are hitting. 

Once you have that you're almost good to go.  You need to tell Github to use that domain.  To do so go back to settings where the Github Pages config is and add the custom domain:

![Screenshot 2020-12-27 222703](https://dev-to-uploads.s3.amazonaws.com/i/8n62gxpe20tsbjlrjre0.png)

If everything goes well you should get to your site via your domain name.  There's a little bit of wonkyness as Github does it's magic and it can take a while to sort out, just watch the Github Pages section for error messages and follow the instructions.
 
# HTTPS

Luckily we don't have to do anything for this as Github will automatically deal with this for us.  They seem to get their certificates from Let's Encrypt.  Note that Github might have trouble with the certificate at first as it does it's thing in the background and issues a certificate. It can take a while so you'll have to go through the scary browser prompt while it's working on it.  Also note that I had trouble hitting my site on normal HTTP at first.

You will want to enforce HTTPS when everything works.  This gives your users better privacy and allows you to use certain types of web APIs.  Specifically you don't want people to direct to an HTTP version or they could be opened to eavesdropping attacks so close this hole.

---

And there we have it a working site with a working domain name.  At least in terms of JS, CSS and HTML you can do whatever you want with it at this point.  Good out there and make some content!

For code you can refer to https://github.com/ndesmic/ndesmic.github.io .  It make or may not look like a complete undesigned and incomplete dumpster fire if you visit it though 😊.