3. Taking a tour
10 Mar, 2024
By now, our project should look like this:
11ty-zones
_data
_includes
_site
images
node_modules
posts
style
.eleventy.js
.eleventyignore
.gitignore
404.md
about.md
archive.njk
changelog.md
(added in 1.1.0)feed.njk
(added in 1.1.0)index.md
LICENSE
package-lock.json
package.json
README.md
(Not seeing a few of these? Files whose names begin with a dot, known as dotfiles, are hidden by default on most systems. Some shortcuts to unhide them: On Windows, see Microsoft's support page (it's different between Windows 10 and 11). On macOS, in Finder, press ⌘ Cmd + ⇧ Shift + ..)
Let's break all this down into parts.
Non-Eleventy stuff
11ty-zones
node_modules
- (various...)
.gitignore
changelog.md
(added in 1.1.0)LICENSE
package-lock.json
package.json
README.md
Good news: You can safely ignore all of these for now! It's nice to know why they're here and what they do, though, so here's a quick rundown.
Git-related
.gitignore
is a list of things for Git (if we're using it) to exclude from our repository and leave untracked: _site
, because we can always rebuild it, and node_modules
, because we can always reinstall it.
Node-related
To recap, package.json
is a manifest with everything Node needs to know about our package, and node_modules
is the folder where npm keeps all of the packages our package depends on.
There's also package-lock.json
. See, I kind of fibbed earlier when I said npm install
uses package.json
to know what versions of what dependencies to install - in truth, it uses package-lock.json
if available. This file is automatically generated by npm and "describes the exact [node_modules
] tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates."
In other words: Between the time I released this project and the time you installed it, some dependency or another may have released a newer version that says it's compatible with the one we need but accidentally breaks things anyway. package-lock.json
makes sure you get the same "known working" versions that I had.
Project info
As with most software, Eleventy-Zones comes with a "read me" file, README.md
. (Codeberg, like most Git hosting providers, treats this as a sort of homepage for the project's repository when you view the repo on the web.) It's really just a one-sentence summary and a link to this demo site, but hey.
changelog.md
, added in 1.1.0, lists the changes made with each release of the project. It's not very long either.
Eleventy-Zones is released under the MIT License; copyright notices and the full text of this license can be found in the LICENSE
file.
Configuration
11ty-zones
.eleventy.js
.eleventyignore
.eleventy.js
is Eleventy's (optional) config file. Whenever I say Eleventy does something "by default," this is probably where to tell it to do something else. (It's also where we can add our own filters and shortcodes, but more on those later.)
Eleventy-Zones mainly sticks with "vanilla" behavior here - my hope is that if this is part of how you're learning Eleventy, it'll mean fewer surprises when you start a new project from scratch. (I was really tempted to change some directory settings, though; for an opinionated example, see this demo site's repo.)
For our project, the config mostly just copies our images and CSS into the build and defines a few custom filters. It also adds Eleventy's HTML <base>
plugin in case you're deploying your site to a subdirectory (like how this demo site is in the 11ty-zones-demo
subdirectory of cypressSap.codeberg.page
). Since 1.1.0, it also also adds Eleventy's RSS plugin (which actually just adds a few more useful filters).
.eleventyignore
is an (also optional) list of things we don't want Eleventy to process. By default, Eleventy builds any *.md
files it finds in our project into HTML pages, but since changelog.md
and README.md
aren't meant to be part of our site, those are listed here.
The output folder
11ty-zones
_site
- (various...)
As mentioned earlier, the output folder Eleventy builds our site into is called _site
by default.
Since this is for the finished product only, you typically won't need to edit anything in here.
Assets
11ty-zones
images
bg_weatherbeaten.gif
favicon.png
style
style.css
When building pages, Eleventy only cares about our template files (which we'll talk about next). This means that, by default, other files like our images and CSS here won't end up in our _site
folder - our config file tells Eleventy to go ahead and copy these over anyway.
Templates
11ty-zones
_includes
base.njk
footer.njk
header.html
post.njk
postlist.njk
posts
2024-02-22-Post-Template.md
404.md
about.md
archive.njk
feed.njk
(added in 1.1.0)index.md
Now for the big one!
Templates are the stars of our project - these files are combined with each other and various sources of data to build finished HTML pages. Eleventy supports a number of different languages for them (which you can mix and match); I've gone with probably the most popular combination:
- Nunjucks (in the
*.njk
files) is a powerful templating language for working with our data. - Markdown (
*.md
) is a lightweight markup language that saves us from writing a lot of basic HTML. (It's used by quite a few online services - you might already use it without even knowing its name.)
Template files often begin with a block of front matter: Some data (usually in human-readable YAML) that the rest of the template, its content, can use.
By default, Eleventy looks for templates throughout the project folder (ignoring the output folder). It doesn't really care how they're organized or what they're named; it builds each into a page following whatever structure we've created.
For example, posts/2024-02-22-Post-Template.md
gets built to _site/posts/2024-02-22-Post-Template/index.html
, archive.njk
gets built to _site/archive/index.html
, and so on. (For index.md
, Eleventy does know that _site/index/index.html
probably isn't what we want, and builds it to _site/index.html
instead.)
You can override this output structure by setting a template's permalink
data property. For example, in 404.md
's front matter, the line permalink: 404.html
tells Eleventy to build to _site/404.html
rather than the default _site/404/index.html
.
Templates don't have to be built into HTML pages - feed.njk
(added in 1.1.0) gets built into _site/feed.xml
, again using the permalink
property. (Templating languages like Nunjucks neither know nor care that we're not building to HTML here; they only look for their own syntax. Without the permalink
, it would be built to the default _site/feed/index.html
, but the contents of that file would be exactly the same.)
Includes
Not all templates are built 1:1 into finished files - special templates called includes are meant to be consumed by other templates instead. Think of them as components that you can reuse in multiple places.
By default, Eleventy looks for include templates in the _includes
folder. (Like "regular" templates, you can name and organize these however you like.)
Layouts
Includes are usually inserted into other templates, but first I'd like to introduce Eleventy's layouts, a special kind of include. These work kind of the opposite way, wrapping around the templates that call them.
_includes/base.njk
is a layout with our standard page structure, and most of our regular templates use it (with the line layout: base.njk
in their front matter). It's mostly boilerplate HTML, peppered with some things like {{ meta.lang }}
and {{ title }}
. In Nunjucks, these double-braces are how we reference variables to be filled in with data when the template is built.
Inside its <main>
element, one variable is used a little differently: {{ content | safe }}
. Variables can be processed through filter functions, and in Nunjucks this is done with a pipe (|
) - so {{ content | safe }}
"pipes" the content
variable through the safe
filter before filling it in. But what are those?
Well, content
is provided by Eleventy: It represents the content of whatever template called the layout, already built into HTML and ready to be wrapped by the layout.
The problem there is that Nunjucks escapes HTML from variables - it changes (for example) "<p>
" into "<p>
" so the browser won't treat it as an HTML tag. That's a good default for security, but if content
were escaped, most of our site would just look like a wall of code! That's why Nunjucks comes with the safe
filter, which stops a variable from being escaped.
Our project has one other layout, _includes/post.njk
, which is used by our blog posts. This one doesn't need all the usual HTML boilerplate because it just uses _includes/base.njk
as its layout - a trick called layout chaining.
Partials
Simple includes, like our site's header and footer, are often called partials. In Nunjucks, we use the include
tag to insert them, like {% include "header.html" %}
.
_includes/header.html
is pretty straightforward - it really is just a fragment of plain old HTML! _includes/footer.njk
does a little more work, using some logical Nunjucks tags (if
, else
, endif
) so the author's name is made into a link only if a URL is provided.
Macros
Nunjucks macros, like the one defined in _includes/postlist.njk
, allow more complexity than partials: They accept arguments so we can pass them data, controlling their behavior.
Our postlist
macro returns a list of blog posts. With the arguments it's passed in archive.njk
, it lists every post, but index.md
passes it different arguments for a list of just the three most recent posts with a link to the archive page.
(Macros aren't the only tool for this kind of situation - Eleventy's shortcodes are another common one. For some pros and cons of different approaches, check out this discussion on GitHub and this post by Jérôme Coupé.)
Data files
11ty-zones
_data
eleventyComputed.js
meta.js
posts
posts.11tydata.js
We've briefly mentioned data and how it can come from various sources, such as a template's front matter. Our project also has some data files that make data available to multiple templates at once.
By default, the _data
folder is where Eleventy looks for global data files. These can be referenced by their filename: _data/meta.js
, which has some metadata about our blog, becomes the global data object meta
. (Remember that meta.lang
variable that _includes/base.njk
referenced?) As usual, you can organize as you see fit - if we moved this file to _data/foo/myImportantData.js
, we'd reference foo.myImportantData.lang
instead.
_data/eleventyComputed.js
is special: Computed data is processed last, after any other data sources, which means it can be calculated from those sources and takes priority over them. (Sorry - this file can't be renamed.) We use this to check if each template already has title
data, then (if it doesn't) generate one from its filename - mimicking Zonelets's behavior.
posts/posts.11tydata.js
is a directory data file that applies only to templates in the posts
folder. (These need the same name as their folder; posts/foo.11tydata.js
wouldn't work.) This file sets the layout
and tags
data (more on tags
in a moment) for all of our blog posts - that way we don't have to add front matter again and again on every new post to set the same thing.
(Now's probably a good time to bring up Eleventy's data cascade, which settles any conflicts between our various data sources. For example, if you do set a layout
in some post's front matter, that one will be used instead of the one set in posts/posts.11tydata.js
, because front matter is higher priority than directory data files.)
As for tags: Eleventy uses our tags
data to group our content into arrays called collections, which are then available in a global data object also called collections
.
Since posts/posts.11tydata.js
gives our posts the "blogPosts
" tag, they'll all be added to collections.blogPosts
. This collection powers our blog's navigation: It's used by the "next/previous post" links at the bottom of _includes/post.njk
, and it's passed to the postlist
macro from both index.md
and archive.njk
.
(tags
is also an array, which makes it an exception to the data cascade: By default, array data gets merged rather than replaced. For example, if we put tags: cats
in some post's front matter, its tags
will end up as ['blogPosts', 'cats']
.)