paint-brush
Build a Gallery Site with 11tyby@james-c-rodgers
2,758 reads
2,758 reads

Build a Gallery Site with 11ty

by James C RodgersNovember 20th, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

This is a run-through of setting up a website with Eleventy, a simple static site generator. I made a gallery site meant to showcase graphic artwork. The site was not maintained and, after several years, the site got hacked and had to be taken down. I am interested in alternatives to Wordpress, partially because I’ve never understood the need for a blogging platform with a MYSQL database just to host static content. I also used some Python scripts to create and modify Markdown files and hosted this site on AWS.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Build a Gallery Site with 11ty
James C Rodgers HackerNoon profile picture

This is a run-through of setting up a website with Eleventy, a simple static site generator. I’m going to try to list out all the relevant steps so you can follow along if you are attempting a similar project. I made a gallery site meant to showcase graphic artwork.

Technologies used in this tutorial are Eleventy, Liquid template language and Bootstrap for responsive CSS. I also used some Python scripts to create and modify Markdown files and hosted this site on AWS in an s3 bucket with CloudFront distribution. You can see the finished product of this project at kingspook.com.

First a quick note about the history of kingspook. My brother Zac was a very talented artist, writer and musician and he constantly created art during his life. King Spook was a character that appeared, usually just his head, frequently in work by Zac during the late 90’s. Tragically, we lost my brother 15 years ago to a hit and run accident. At the time, my mother created a website to showcase his art.

She had wanted to get something up quickly and easily so she created a Wordpress site. She uploaded many pictures of my brother’s art and put it out on the domain ‘kingspook.com’. The site was not maintained and, after several years, the site got hacked and had to be taken down. By that point, I was trying to learn web design and so it became a project that I needed to take over and has been in the back of my mind since. This year I decided to get it up and running again.

I am interested in alternatives to Wordpress, partially because of what happened to the original kingspook.com and because I’ve never understood the need for a blogging platform with a MYSQL database just to host static content. I have used Jekyll to build a site in the past and I thought that it worked pretty great. But, Jekyll is based on Ruby and I found that dealing with Ruby and RVM was kind of a pain, probably because Ruby is not a language I know. So, I went looking for something that was based on Node.js and found 11ty, which is basically a Jekyll clone that is based on Node and javascript.

The documentation is a little thin but there are some good walkthroughs that others have done and I was able to jump between a few of them to get where I needed to get with it. You can find several tutorials here on the official site. I had used Liquid template language before with Jekyll so I used Liquid with this project. That said, 11ty is designed to be template agnostic so you could use Mustache, Handlebars, Nunjucks or other languages you may know, see a full list in the docs. I like this approach of being agnostic on the template language and allowing you to use what you know.

Part 1 - Building the Site Structure

Let’s get started…

First things first, I created my project folder, opened it in the terminal, initialized a git repo, made a README and pushed it up to git.

Then I Initialized a new npm package with ‘npm init’ and followed the prompts. So, at this point, the project folder is a git repo with a package.json file created by npm.

Now the 11ty stuff-

To install 11ty and start an eleventy project run the following from the terminal in your project folder- 

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

Next I created a folder named ‘_includes’. This is where I put html and liquid files which eleventy will use at build time. These can be layouts or items like the header and footer which can be pulled into your pages with the ‘include’ keyword. 

Then I created an  ‘assets’ folder. Inside ‘assets’ I’ve got my css file and the images and other files I have on the site. These files will need to be pulled in at build time. In order to do that I needed to create a file named ‘.eleventy.js’ in the project root with the following contents -

eleventy.js

module.exports = function (eleventyConfig) {
   eleventyConfig.addPassthroughCopy('assets')
   return {
       passthroughFileCopy: true
   }
}

This makes it so that the ‘assets’ folder and everything in it will be copied into the ‘_site’ folder at build time. 

Next, I created a ‘pages’ folder. This is for all my static pages. Inside this folder I’ve got an ‘about.html’ and a ‘gallery.liquid’ which reflect my two top-level nav items. Also, I’ve got a folder named ‘art’ which contains a markdown file for each image file I want to display on the gallery page and which also represents its own page. More on this later.

Next, I have ‘.gitignore’ and ‘.eleventyignore’ files. The ‘.eleventyignore’ file works very similarly to the ‘.gitignore’ in that this is where you list files and folders which will be ignored at build time.

My .eleventyignore

README.md
ignore
node_modules

My .gitignore

package-lock.json
node_modules

I created an ‘ignore’ folder where I put my scripts which do not need to be included in the build.

Finally, I’ve got the ‘index.html’ and ‘error.html’ files. ‘index.html’ is the home page of the site and ‘error.html’ is there to catch bad navigation or broken links, etc. More on both of these later.

To review - At this point in the build our project directory contains these files: .eleventy.js, .eleventyignore, .gitignore, error.html, index.html, package-lock.json, package.json and README.md.

And these folders: _includes, assets, ignore, node_modules and pages

Some scripts need to be added to the ‘package.json’ for the eleventy commands and to deploy to s3.

I have these lines in the ‘scripts’ area of my ‘package.json’ -

 "scripts": {
   "build": "eleventy",
   "serve": "eleventy --serve",
   "debug": "DEBUG=Eleventy* eleventy",
   "deploy": "aws s3 sync _site/ s3://BUCKET_NAME"
 },

If you are using this for your own project, replace ‘BUCKET_NAME’ with the name of your s3 bucket, also you’ll need to have some set-up done to push to AWS from the command line, more on this later. If you are not deploying to s3 you do not need the “deploy” line, or you might want to replace this with a script for your own deployment method.

These scripts are run from the terminal like so - ‘npm run serve’

The ‘build’ script will compile the website into the ‘_site’ folder. This folder will be created if it does not exist.

The ‘serve’ script will serve the ‘_site’ folder on a local port on your computer, generally ‘localhost:8080’. It also will compile to the ‘_site’ folder as in the ‘build’ command and will update live by re-compiling anytime you save changes or add a file to the project folder. This is extremely useful when troubleshooting or tinkering with css.

The ‘debug’ script will output copious amounts of debug info to the terminal. I did use this at one point and it did help me track down what was causing my site not to compile. More on this in docs.

At this point, I had the basic outline of the site and it was time to start leveraging Liquid to flesh it out. 

Part 2 - Building the Site Files

Let me just explain a little about how this works in general terms and then I’ll run through how I put it to use.

Using Liquid we can make a ‘layout’ by creating a file with the ‘.liquid’ file extension. This will resemble an HTML file but with the ability to use variables inside of curly braces like so, {{ variable-name }}. Then we can have a Markdown file, which will represent the web page and it can reference the layout and pass variables to the layout using something called YAML Front Matter.

YAML Front Matter is everything between a set of two ‘---’  at the top of the markdown file. It is used to set a layout, define variables or assign ‘tags’, which can be used for organizing pages, and set a date variable which can be used to order things chronologically. Everything below the front matter will be passed to the layout in place of {{ content }} on the final page. Layouts can be extended by creating a new ‘.liquid’ file and adding front matter with a reference to the original layout, you’ll see this below. Also, we can add front matter to a plain html file and eleventy will read it and use it just the same, there will be examples of this below as well.

Another thing you can do is make HTML files which contain only portions of the page like the nav, header, footer or sidebar. These files, along with any layouts, go into the ‘_includes’ folder where eleventy will find them and compile into the web pages at build time. These elements can be pulled into a liquid layout with the keyword ‘include’ like so {{ include header.html }}, whatever html you have in the ‘header.html’ file will be compiled into the final page in place of {{ include header.html }}.

Liquid also has some conditional statements and loops which can be referenced within {% %}, I’ll have examples of this below.

To learn more about Liquid template language go to the projects repo, https://shopify.github.io/liquid/.

First, I needed the basic layout of the site, for this I created a ‘base.liquid’ file in the ‘_includes’ folder. This file serves as the basis of the whole site, it will be replicated when the site compiles anytime it is referred to as the ‘layout’ in the front matter of a page.

This file has everything I want in the head so, the links to bootstrap and the css file as well as fonts and the favicon. Then it has the header and the nav elements with the footer on the bottom. I then extend the basic layout to add more content later. Notice the {{ content }} tag, this is where any page using this template or even another template extending this one will populate when the pages compile at build time. At the bottom are the scripts for bootstrap as well.

base.liquid

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8"/>
        <meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"/>
        <title>King Spook</title>
        <link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" rel="stylesheet"/>
        <link href="/assets/css/main.css" rel="stylesheet" type="text/css"/>
        <link href="https://fonts.googleapis.com/css2?family=McLaren&family=Special+Elite&display=swap" rel="stylesheet">
        <link href="/assets/img/KingSpookIcon.ico" rel="shortcut icon" type="image/x-icon">
    </head>

    <body>
        {% include header.html %}
        {% include nav.liquid %}
        <div class="container-fluid">


            <main class="container">
                {{ content }}
            </main>

            {% include footer.html %}

        </div>
        <script crossorigin="anonymous" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
        <script crossorigin="anonymous" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"></script>
        <script crossorigin="anonymous" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
    </body>
</html>

Before this will work I needed to make the nav, header and footer elements. 

These files go inside the "_includes" directory...

header.html

<header>
   <div class="container">
       <img alt="king spook image" class="img-fluid" src="/assets/img/kingSpook-bw-fromTotem.png"/></div>
   <div class="container">
       <a href="/">
           <h1 style="text-align: center;">King Spook</h1>
       </a>
   </div>
   <div class="container"><img alt="king spook image" class="img-fluid" src="/assets/img/kingSpook-bw-fromTotem-flipped.png" style="float:right"/></div>
</header>

footer.html

<footer class="flex-container-row centered">
   <span>&hearts; &copy; 2020</span>
   <img alt="JR" class="icon" src="/assets/img/JRsymbol.png"/>
   <span>JCR &hearts;</span>
</footer>

Those two are pretty straight forward.

The nav element will take a little more explaining and let me show some of the capability of collections. The nav element is a liquid file which makes use of collections and liquid’s for loop capability.

Collections are created whenever you use the ‘tags’ keyword in the front matter. When eleventy is compiling the site files it will create collections based on tags it finds in the front matter. Pages can have multiple tags which allow them to be grouped into multiple collections. Collections can be looped through with liquid or another template language. 

One of the ways I use tags is to have dynamic navigation, any page that is on the top level navigation I give the tag ‘nav’, then I can loop through it by accessing ‘collections.nav’. This method is really unnecessary here since the nav for this site only has two elements in it but I like designing with expandability in mind.

So, in the code below, I access the url of the page with {{ nav.url }} this is the relative path to the page that eleventy creates when compiling the site, eleventy creates this variable at build time. Any variables I have defined in the front matter will be grouped into the ‘data’ object thus, {{ nav.data.title }} gives me the title I defined in the front matter of the file, you’ll see the other side of this when I get to the next step.

nav.liquid

<nav class="flex-container-row">
 {% for nav in collections.nav %}
 <a href="{{ nav.url }}" class="nav-link">{{ nav.data.title }}</a>
 {% endfor %}
</nav>

Now that we have our base template with header, footer and nav, let’s see how it comes together to make a page.

The home page is an index.html file in the project root, mine looks like this…

index.html

---
layout: base.liquid
permalink: /
---
<div class="flex-container-column centered">
   <a href="/pages/art/l/Be-No-Peace/"><img alt="welcome!" class="img-fluid" src="/assets/img/Welcome-from-Zac.jpg"/></a>
   <p>
       King Spook welcomes you...
   </p>
</div>

Because we are referencing the base.liquid layout, these couple of lines are going to create a full web page with the header, footer and nav in place. The permalink keyword in the front matter will overwrite the path when eleventy is compiling the site, in this instance, I’m using it to set this page at the root of the site.

The error file just has the layout set in the front matter and the content is a picture and an error message. I do not have the ‘permalink’ keyword on error.html so eleventy creates an ‘error’ folder and writes an index.html file in it when it compiles to the '_site' folder. The best way to get this is to try it yourself and look at the '_site' folder after running 'npm run build'.

error.html

---
layout: base.liquid
---
<p>
   King Spook says... "Hmm, seems like you are lost?"
</p>
<div class="flex-container-column centered"><img alt="art" class="img-fluid" src="/assets/img/art/kingspook-color.jpg"/></div>

If you were following along, at this point in the build, there is a home page and an error page. The home page would display pretty close to what you can see at kingspook.com now except there would be nothing in the nav bar because we haven’t built those pages yet. The link that surrounds the welcome picture would be broken also since we haven't made any of the art pages yet. But I think you can see how this is starting to fit together and how 11ty leverages the template language to build sites quickly and with flexibility.

An aside about Bootstrap. The Bootstrap piece is pretty minimal in this project, I'm really just bringing it in to make the design responsive. All the css class references in my code to "container", "container-fluid" and "img-fluid" are bootstrap classes which are doing the work of resizing elements and images with changes in screen size.

Ok, let’s go through setting up our static pages. 

Inside the ‘pages’ folder I’ve got ‘about.html’ and ‘gallery.liquid’. The about page is a basic webpage with text and pictures so I just used html. The gallery page is leveraging liquid to loop through all the art and display a smaller image of the picture which also links to a page for each picture.

I’m just going to show the top of the about page for brevity…

about.html

---
layout: base.liquid
title: About
tags: nav
date: 2020-01-01
---
<h3>
   About
</h3>

Eleventy will automatically sort things by date if there is a date field present in the front matter, if dates are the same, then it will sort alphabetically. But if there is no date, then items in a collection will sort randomly. By including the date I can ensure that when eleventy compiles it will always show ‘About’ to the left of ‘Gallery’ in the nav bar. The text displayed in the nav bar is the ‘title’ from the front matter.

In the gallery.liquid file we can see more examples of the power of using a template language. The images that I am working with are all different sizes but I found that if I grouped them into two collections, long and short, I could display them pretty nicely in the gallery. On the gallery page, I loop through both of them and display the image and the title in a div which is a clickable link to the page for that image. 

gallery.liquid

---
layout: base.liquid
title: Gallery
tags: nav
date: 2020-01-01
---
<h3>Gallery</h3>
<div class="flex-container-row centered">
   {% for pic in collections.long %}
       <a href="{{ pic.url }}">
           <div class="gallery-pic-container">
               <div class="flex-container-column centered">
                   <img class="gallery-pic" src="{{ pic.data.path }}" alt="{{ pic.data.name }}"/>
               </div>
               <span class="pic-label">{{ pic.data.name }}</span>
           </div>
       </a>
   {% endfor %}
   {% for pic in collections.short %}
       <a href="{{ pic.url }}">
           <div class="gallery-pic-container-short">
               <div class="flex-container-column centered"><img class="gallery-pic" src="{{ pic.data.path }}" alt="{{ pic.data.name }}"/></div>
               <span class="pic-label">{{ pic.data.name }}</span>
           </div>
       </a>
   {% endfor %}

Just to touch on the css here for a minute, because it took me a bit of time to work it out. I’m using a combination of flexbox and grid to display these pictures in the gallery. The outer container is a flexbox with ‘flex-direction: row’ and flex-wrap: wrap’ so that the inner containers will display one to three per row depending on the display width. The inner containers have a static height and are made with a css grid so that the text will stick to the bottom of the box and everything will line up on the row. The picture container has ‘display: grid’ and ‘grid-template-rows: 4fr 1fr’. The picture is set to row 1 and the picture label to row 2 with ‘align-self: end’ which makes the text stick to the bottom. You can look at my github repo if you’re interested in digging into it.

So, the collections that are being looped through to create this page are made from markdown files which are based on image files. Creating these markdown files is where the python comes in.

Part 3 - Making the markdown files

Let me show an example of one of these files and then I’ll break down how I made them and how it all works.

constrained.md

---
name: Constrained
path: "/assets/img/art/constrained.jpg"
date: 2020-04-14
previous: /pages/art/l/casual-space-shock/
next: /pages/art/l/dragon-moon-king-spook/
---

First off, you’ll notice that this file is only front matter. When this file is displayed as its own page, everything that you see on the page is part of the layout with just the name and picture changing. Which raises another point, there is no layout in this front matter. That is because of a little trick that eleventy allows where you can give all the files in the same folder the same layout.

But let’s start with looking at how this file interacts with ‘gallery.liquid’. We can see that we have the path to the file to display and we have the name to display under the picture. The ‘date’ field will determine the order that the art displays on the gallery page so that it will be consistent, this becomes more important when we look at the ‘previous’ and ‘next’ fields which we will do below.

Let’s talk about that little trick that eleventy allows to apply settings across all the files in a folder. If you have a group of files that are all going to have some of the same front matter, you can put them in a folder and create a json file with the same name as the folder. So, for all my markdown files that need to be in the ‘long’ collection, I put them in a folder named ‘l’ and created a file in that folder named ‘l.json’. The json file name must match the containing folder name. Whatever I put into the json file will be read as if it is in the front matter of all the files in that folder.

l.json

{
 "layout": "art.liquid",
 "tags": ["art", "long"]
}

So all my markdown files in this ‘l’ folder will have ‘layout: art.liquid’ and ‘tags: [‘art’, ‘long’] applied as if it was in their front matter.

Now let’s review what’s happening here because it is a good illustration of how eleventy builds sites.

When eleventy compiles the site it is going to make an ‘index.html’ file for each of these markdown files. This will be in a folder named whatever the markdown file was named and the containing folder structure will remain the same so, since I’ve got ‘pages/art/l/constrained.md’, it was output to ‘_site/pages/art/l/constrained/index.html’.

The url will be captured as part of the build process into a  collection named ‘long’, the other front matter will be collected into a data object for each markdawn file. When we loop through this collection in a liquid file, these variables will be plugged in to the html so that we can have the right picture with the right link display on the page.

I had over 50 image files that I wanted to create pages for and display on the gallery page. That is a lot of data entry with opportunity for typos if I were to just build those files manually. I have been working on my Python skills lately and saw this as an opportunity to write some scripts to take care of the repetitive parts.

I went through a few iterations to get to the final place and I don’t want to go through all my incremental progress here so, I’ve simplified my actual process. Let’s say this is the original script file I used to create the markdown files.

create_files.py

import os
pics = os.listdir('assets/img/art')
for pic in pics:
   name = pic[0:-4]
   strng = f"""---
name: {name}
path: "/assets/img/art/{pic}"
---
"""
   file_name = 'pages/art/' + name + '.md'
   with open(file_name, 'w') as new_file:
       new_file.write(strng)

Basically, this gets a list of all the image files to display and creates a markdown file with the front matter and the name of the file as the name of the picture and the path to the image to display.

At this point in the project I had pictures displayed in the gallery page and I had a page for each picture, pretty good. I had to re-organize the pictures into long and short so they would look good on the gallery page, which I had to do manually. I also had to edit most of the image names from what the files were named, also a manual job. But once I had those things cleared up I wanted to have the pictures so that when you were looking at them in the full page view, you could click through them one by one, forward and backward without needing to return to the gallery page.

This turned out to be a little trickier than it first appeared. For one thing, without date tags, the pictures would not compile in the same order so anytime the site went through a build the gallery page could appear in a different order. Also, eleventy builds the collections so that the files could be looped through at build time but I wouldn't have access to that collection as an array to loop through on the live site later. 

I tried a few different things which didn’t work but then I realized the solution was a linked list. Actually, a doubly linked list is what I needed; a doubly linked list is a data structure where each item has a link to the previous item in the list and to the next item in the list. If I turned my markdown files into a linked list, then I could have forward and back buttons which allowed the user to navigate through the art files and all I really needed to do was add previous and next fields to the front matter of each file.

The first image would have its previous set to nothing and the last would have its next set to nothing. I would also need to set the date in the front matter of each file so that the order that you saw when navigating one at a time would match the order on the gallery page and this wouldn’t change when the site compiled again.

I worked up a script to do this, there's a lot happening here so I've documented the code with comments.

paginate.py

import os
 
def get_date_list(month, day):
   # increment date by one day, return date as list with two strings
   day = int(day)
   day += 1
   month = month
   if day > 30:
       day = '01'
       month = '05'
   elif day < 10:
       day = f"0{day}"
   else:
       day = str(day)
   return [month, day]
 
def process_file(filename, prev, dates):
   # read existing file and return text with new lines added
   date_string = f"date: 2020-{dates[0]}-{dates[1]}\n"
   new_lines = f"{date_string}previous: {prev}\n"
   new_content = ''
   count = 0
 
   with open(filename, 'r') as read_file:
       for line in read_file:
           text = line
           if '---' in line:
               count += 1
               if count > 1:
                   text = new_lines
           new_content += text
       return new_content
 
# instantiate variables, date starts in april
month = '04'
day = 0
prev = ''
files_list = []
 
# first, loop through 'long' files and add to files_list
# then loop through 'short' files and add to files_list
# the 'previous' is set to the last files path and the last item in the files_list gets updated with the 'next' set to the current file's path
# on the final file 'next' is set with an empty value
 
art_l = os.listdir('pages/art/l')
art_l.sort()
 
for i, file in enumerate(art_l):
   filename = f"pages/art/l/{file}"
   compiled_path = f"/{filename[0:-3]}/"
   if not '.json' in filename:
       dates = get_date_list(month, day)
       month = dates[0]
       day = dates[1]
       new_text = process_file(filename, prev, dates)
       prev = compiled_path
       if i > 0:
           files_list[-1][1] += f"next: {compiled_path}\n---\n"
       files_list.append([filename, new_text])
 
art_s = os.listdir('pages/art/s')
art_s.sort()
 
for i, file in enumerate(art_s):
   filename = f"pages/art/s/{file}"
   compiled_path = f"/{filename[0:-3]}/"
   if not '.json' in filename:
       dates = get_date_list(month, day)
       month = dates[0]
       day = dates[1]
       new_text = process_file(filename, prev, dates)
       prev = compiled_path
       files_list[-1][1] += f"next: {compiled_path}\n---\n"
       if i == len(art_s) - 1:
           new_text += 'next: \n---\n'
       files_list.append([filename, new_text])
 
# once the files_list is complete, loop through and rewrite each file with the updated content
for item in files_list:
   with open(item[0], 'w') as write_file:
       write_file.write(item[1])

There are a few different moving parts here but this is the gist. The script loops through all the files and creates a list of all the file paths and their contents. Each file is represented by another list object with the file path in the first index and the file contents in the second index. As the script goes through the files it adds the ‘date’ field and the previous ‘field’ to the file contents, the previous field is set to the path of the last file processed.

Before it appends the current file to the list, it opens the last file in the list and adds a ‘next’ field which is set to the path of the current file being processed. In the case of the last file, it adds the ‘next’ field with no value. Once all the files have been processed, it loops through the file list and over-writes all the files with the new content. In the end, you have a group of markdown files, sorted by a date field, which act as a doubly linked list because they have pointers to the previous and next items in the list.

The last thing to show here is the layout for the individual pages for each image.

art.liquid

---
layout: base.liquid
---

<div class="flex-container-column">
  <div class="page-controls container">
    {% if previous %}
    <a href="{{ previous }}">
      <img
        alt="arrow-left"
        class="arrows"
        src="/assets/img/arrow-left-white.png"
      />
    </a>
    {% else %}
    <img
      alt="arrow-left"
      class="hidden arrows"
      src="/assets/img/arrow-left-white.png"
    />
    {% endif %}
    <div class="art-label">
      <h3>{{ name }}</h3>
    </div>
    {% if next %}
    <a href="{{ next }}">
      <img
        alt="arrow-right"
        class="arrows"
        src="/assets/img/arrow-right-white.png"
      />
    </a>
    {% else %}
    <img
      alt="arrow-right"
      class="hidden arrows"
      src="/assets/img/arrow-right-white.png"
    />
    {% endif %}
  </div>
  {% if next %}
  <a href="{{ next }}"
    ><div class="flex-container-column centered art">
      <img class="img-fluid" src="{{ path }}" alt="{{ name }}" /></div
  ></a>
  {% else %}
  <div class="flex-container-column centered art">
    <img class="img-fluid" src="{{ path }}" alt="{{ name }}" />
  </div>
  {% endif %}
</div>

You can see that this layout extends the base layout so everything in this layout will go in place of {{ content }} on the base.liquid layout. There is a little bit of use of conditionals here to hide the previous and next arrows on the first and last images, respectively. Also, I designed it so you could easily click through all the pictures one by one, so if there is a next picture, clicking the current picture will navigate to the next picture. Otherwise, we are doing the same stuff we’ve seen before, using the content from the markdown files to flesh out the layout and make our final page.

Now we’ve got the full website built, we can think about deploying it.

Part 4 - Deploy

I have an AWS account that I use for most of my projects. s3, which stands for Simple Storage Service, allows for easily hosting a static site on AWS. This makes it an ideal place to host a static website for almost no cost.

There are a lot of good resources that walk through configuring your s3 bucket for web hosting including in AWS’s documentation here. Also, there’s a really good blog post I came across recently that walks through all of it including the cloudFront piece, read it here. So, I don’t feel like I need to cover that part in this article. But, I do want to share some tricks I’ve put together to help the deployment of your 11ty project.

First, set up your environment to have your AWS credentials stored so that you can use the CLI without having to plug in your access keys. Read the docs about it here for the full details. For a condensed version, create a directory named ‘.aws’ in your home folder.

Inside that folder, create a file named ‘credentials’. Inside of this file, you will define a default region and your AWS access key and your AWS Secret Key. You can also define multiple profiles in this file if you interact with more than one AWS account or use different AWS credentials.

.aws/credentials

[default]
aws_access_key_id = XXXXXXXXXXXXXXXXX
aws_secret_access_key = XxXXXXxxxXXXxXxXxXXXxXXxxXXxXxXXXxxXXxXx
region = us-east-1
[other_profile]
aws_access_key_id = XXXXXXXXXXXXXXXXX
aws_secret_access_key = XxXXXXxxxXXXxXxXxXXXxXXxxXXxXxXXXxxXXxXx
region = us-east-1

To get the access keys, go into IAM and click on your user, then click on “Security Credentials” and click “Create Access Key”. This will create an Access key and Secret Access key that has the same access as the user you create them for. Note: you will only have access to the Secret Access Key at the moment you create it so copy it or download the credentials as a .csv.

Next, if you recall I had added a script to the package.json file that looked like this…

"deploy": "aws s3 sync _site/ s3://BUCKET_NAME"

Now let me show you how we can put this to use. You need to make sure you put the name of your bucket in place of “BUCKET_NAME” in the script. If you run “npm run deploy” it will run the command to copy the contents of the “_site” folder up to your s3 bucket and, just like that, your site is live for the world to see. It is best practice to run “npm run build” which will compile your site into the “_site folder” before you deploy. 

FYI, you can specify a non-default profile from your credentials by adding the “--profile” flag and the name of the profile to the command after the bucket name.

That would look like…

"deploy": "aws s3 sync _site/ s3://BUCKET_NAME --profile other_profile"

That about does it. One thing I want to point out here that I find annoying is that when 11ty builds the site, it will not clean up files from the '_site' folder. So, if you delete files or remove images from your project, they will still be in your _site folder unless you manually delete them. The same holds true for deploying to s3, it is copying everything in the '_site' folder up to your s3 bucket but not removing files that are no longer in use. It is a good thing to keep an eye on especially if you do a major overhaul of the site.

Thanks for following along, I hope you found it helpful!

James Rodgers

Software Engineer

AWS Certified Cloud Practitioner