Building a Website Using 11ty

Ok, I admit. I have a thing for static site generators. It started with Jekyll; after that, Gatsby was my weapon of choice for a while. But new tools keep popping up every month, and my new favorite, without a doubt, is 11ty.

In the first article of my web development in 2022 series, I will show how I use 11ty to create this site.

Most web developers are probably familiar with static site generators by now. They are flexible and powerful tools to create websites that don’t rely on databases, PHP runtimes, or even JavaScript. Instead, they generate plain HTML, CSS, and images just like the web used to be. You can add JavaScript if needed, but the basic experience remains fast, secure, and almost maintenance-free.

But flexible tools can quickly become too complicated, and I feel that Gatsby suffered that fate. Every time I opened one of my Gatsby projects and updated its dependencies, something broke. And even though GraphQL (that Gatsby uses as its data layer) sounded like a cool idea at the time, it may be overkill for a static website.

So when I rebuilt this site, I switched to 11ty, and I haven’t regretted it for a second. This article will guide you through my setup, starting with the simple pages. It is the first article in a series where I will also cover more complex pages, social media, performance, accessibility, and a list of other topics. I will not show you every possible way to do it; instead, I will show you how I’ve done it. If it suits you – great – if not, use it as a starting point.

Getting started

This article will not show you every piece of code needed to create a website. Instead, I will explain how everything fits together and zoom in on the more exciting parts. So instead of listing every command needed to get started, I’ve created a complete starter project that you should download from GitHub. So, download it, run npm install followed by npm start, and the website should start. The rest of this, and future, articles will reference files in this project. Let’s walk through the pieces one by one.

Content

Every website project should start with some content. For 11ty, the content is stored in Markdown files in the folder /content. The actual text of the files is plain Markdown but what makes static site generators so flexible is that you can combine this with YAML frontmatter to associate any structured data with the content.

As an example, you’ll find the file 2022-10-02-introduction.md listed below. As you can see, it has some content, but it also has metadata such as the layout to use when rendering the article (more on that later), the title of the article, and even some tags to organize the articles.

---
layout: ArticleLayout.jsx
title: Part 1, Introduction
tags: [article]
---

Ok, I admit. I have a thing for static site generators. It started with Jekyll; after that, Gatsby
was my weapon of choice for a while. But new tools keep popping up every month, and my new favorite,
without a doubt, is 11ty.

In the first article of my web development in 2022 series, I will show how I use 11ty to create this
site.

---

Most web developers are...

Each Markdown file will create its own page. So if you want to create a page at the url /cool/, you create a file called index.md in the folder /content/cool. You can always override the url of a page by adding permalink: /not-cool/ in the frontmatter data.

A Markdown file is a trigger to create pages. As we’ll see in a later article, one Markdown file can create multiple pages, but no Markdown file = no page.

Images, css and other assets

The next piece of content is images, css and other assets. Different types of assets need to be handled differently. Images, for instance, should be optimized, resized to the correct size, and delivered to the browser in the best format possible. For this reason, I place all assets in the folder /src/assets.

I will return to this folder in a later article where I’ll discuss performance. For now, I’ve configured 11ty to simply copy all assets to the output folder. So if you add an image to /src/assets/image.png, you can reference it as /assets/image.png in your pages.

Layouts

All static site generators use some kind of templating engine to lay out your pages. 11ty is a bit different here since it allows you to choose from many different engines (such as Nunjucks, Liquid, and Handlebars). Coming from Gatsby and the React world, they all looked kind of dated. Fortunately, you can add more engines through plugins, so I use eleventy-hast-jsx. This plugin brings the simplicity of JSX and the full power of JavaScript to your layouts.

Let’s start with the most straightforward layout, the about page in /src/layouts/AboutLayout.jsx.

const { Raw } = require("eleventy-hast-jsx")
const Navigation = require("../components/Navigation")

function AboutLayout({ content, page, title }) {
  return (
    <>
      <Navigation url={page.url} />
      <main>
        <article>
          <header>
            <h1>{title}</h1>
          </header>
          <Raw html={content} />
        </article>
      </main>
    </>
  )
}

module.exports = {
  default: AboutLayout,
  data: {
    layout: "BaseLayout.jsx"
  }
}

There is a lot to unpack here, so let’s start from the top.

Components

Using JSX and JavaScript, you can structure your layouts just like you would any React application – through components. 11ty has a general concept called shortcodes that provide a similar feature across all templating engines. I prefer the React way of doing it, so I’ve just ignored that feature altogether.

This is the /src/components/Navigation.jsx component.

const items = [
  { href: "/", text: "Blog" },
  { href: "/archive", text: "Archive" },
  { href: "/about", text: "About" }
]

function Navigation({ url = "" }) {
  const activeIndex = items.reduce((previousValue, item, index) => {
    return url.startsWith(item.href) ? index : previousValue
  }, -1)

  return (
    <nav>
      <ul>
        {items.map((item, index) => (
          <li className={activeIndex === index ? "active" : ""}>
            <a href={item.href}>{item.text}</a>
          </li>
        ))}
      </ul>
    </nav>
  )
}

module.exports = Navigation

If you are familiar with React, this should look very familiar. We get our props as arguments to the function and deconstruct them directly. We then use standard JavaScript and JSX to generate the HTML that we want to output.

Note. The goal is to create a static HTML page, so no click handlers or other interactive parts of React are allowed – just plain HTML elements. I may get back to interactive components in a later article.

Components can be composed of other components, so you can create an entire design system using components if you want to.

Data

If you go back to the layout, your eyes will probably stop directly at the function declaration.

function AboutLayout({ content, page, title }) {

This function creates a standard React component that gets its content, title, and a page object from the props, but who provides these props? 11ty! 11ty passes a lot of data to our layouts, including the page object and all data from the Markdown file.

Remember the layout key in the Markdown file earlier? A Markdown file triggers a page to be created, and the layout key specifies which layout to use. This is what causes the AboutLayout function above to be invoked at all, and as part of that invokation, 11ty provides the props.

In this case, content is the text from the Markdown file, the title comes from the frontmatter data, and the page is a special 11ty object. Data can come from many different sources though, and the 11ty documentation has an entire section about this. My suggestion – test it! Replace the deconstruction in the function with a single props variable and print it using console.log(JSON.stringify(props, null, 2). This will give you an idea of what data you have available.

Base layouts

One final thing to note about layouts is that they can be nested. In the example above, you may have noticed that just like the Markdown file had a layout property, the layout itself exported a layout property. In this case, it points to BaseLayout.jsx. This way, we can nest layouts. You may have wondered where I put all the styling and other HTML stuff. This is the place.

const { DOCTYPE, Raw } = require("eleventy-hast-jsx")

function BaseLayout({ content, title }) {
  return (
    <>
      <DOCTYPE />
      <html lang="en">
        <head>
          <meta charSet="utf-8" />
          <title>{title}</title>
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <link rel="stylesheet" href="/assets/styles.css" />
        </head>
        <body>
          <Raw html={content} />
        </body>
      </html>
    </>
  )
}

module.exports = {
  default: BaseLayout
}

Building

These are the basic building blocks to getting started. Create your content in Markdown files, add images, css, and other assets to your assets folder and create the layouts you need. If you need a little structure, break up the layouts into components.

The final step is to run npm run build to create a static website in the build folder, copy the folder to your hosting provider of choice, and enjoy your beautiful website.

That’s it. In the next article, I will turn up the complexity a bit by creating the blog and archive pages. I will also give you a few tips and tricks.