🖥️ Updates to the Eleventy guide
Table of Contents
Intro
This is an updated version of my Migrating to Eleventy post from two years ago! This version is current as of 2025 July so if you are coming from the future (hi!!) and see something doesn’t work anymore, please let me know so I can make a new one o’ these.
Anyway, let’s get to it! Let’s make a blog in Eleventy!
Install everything
Eleventy requires Node.js version 18 or higher, so go grab that first.
Next, make a directory that will house your website. For example, I made /media/DATA/my cool blog/
. (If you’re in Windows, you can make it something like C:\Users\Your Name\Documents\my cool blog\
or whatever.) Go into that folder, right-click, and select Open in Terminal.
Once that’s open, type in:
npm init -y
npm install @11ty/eleventy
This will get Eleventy set up in your project folder.
While we’ve got our terminal open, I want to install some add-ons we’ll be using later. The first is Luxon, which will let us display our date and time.
npm install luxon --save-dev
The second is the 11ty RSS plugin, which as you’d imagine, will let us make an RSS feed.
npm install @11ty/eleventy-plugin-rss
Configure Eleventy
Create the .eleventy.js file
Go into your project directory and add a file named .eleventy.js
and enter this in:
/my cool blog/.eleventy.js
// Tells Eleventy to look for the Luxon plugin
const { DateTime } = require('luxon');
// Tells Eleventy to look for the RSS plugin
const pluginRss = require("@11ty/eleventy-plugin-rss");
// This is all the stuff that Eleventy is going to process when it exports your site
module.exports = function (eleventyConfig) {
// Edit these to include your images, CSS, and other folders and files that you want to copy over to your public folder. "addPassThroughCopy" means it will copy those files over as-is without processing them, and "addWatchTarget" means it tracks changes and updates live when you use the --serve command
eleventyConfig.addPassthroughCopy("./src/styles");
eleventyConfig.addWatchTarget("./src/styles/");
eleventyConfig.addPassthroughCopy("./src/images");
eleventyConfig.addWatchTarget("./src/images/");
eleventyConfig.addPassthroughCopy("./src/not_found.html");
eleventyConfig.addPassthroughCopy("./src/.htaccess");
// Load the RSS plugin
eleventyConfig.addPlugin(pluginRss);
// Adds Next & Previous links to the bottom of our blog posts
eleventyConfig.addCollection("posts", function(collection) {
const coll = collection.getFilteredByTag("posts");
for(let i = 0; i < coll.length ; i++) {
const prevPost = coll[i-1];
const nextPost = coll[i + 1];
coll[i].data["prevPost"] = prevPost;
coll[i].data["nextPost"] = nextPost;
}
return coll;
});
// Return the length of a collection for tag clouds (thank you Claus!!)
eleventyConfig.addFilter('length', (collection) => {
return collection[1].length;
});
// Add the filter "readableDate" to simplify the way blog dates are presented
eleventyConfig.addFilter('readableDate', (dateObj) => {
return DateTime.fromJSDate(dateObj, { zone: 'utc+9' }).toFormat(
'yyyy-LL-dd'
);
});
// Slices up RSS posts
eleventyConfig.addFilter("head", function (arr=[], count=1) {
if (count < 0) {
return arr.slice(count);
}
return arr.slice(0, count);
});
// Add the filter "topDate" to simplify the way blog dates are presented at the top of blog posts
eleventyConfig.addFilter('topDate', (dateObj) => {
return DateTime.fromJSDate(dateObj, { zone: 'utc+9' }).toFormat(
'yyyy LLLL dd'
);
});
// These are the folders that Eleventy will use. "src" is where you edit files that Eleventy will then take in and export into "public," which you upload.
return {
dir: {
input: "src",
output: "public",
},
};
};
It’s kind of a lot, but we’ll go over each part later in due time. For now, save this file.
Set up your src directory
Next, make a directory in your project directory named /src/
. Right now, my project directory looks like this:
Open your /src/
directory and make a new folder named /_includes/
. This is where your templates are going to live. Also add your /images/
and /styles/
folders. I am going to add favicon.png
, cat.jpg
, mario.jpg
, and bg.gif
into /images/
for this guide.
Set up your top-level pages and base.njk
Create index.md
In your /src/
folder, make a file named index.md
and enter this in:
/my cool blog/src/index.md
---
layout: base.njk
title: My Cool Blog
description: This blog is totally sweet, right?
featured_image: favicon.png
---
Thanks for visiting my super sweet blog! This is an introduction post of some kind.
[Here's a link in Markdown](https://time.is/).
<a href="https://time.is">You can also write it in HTML</a>.
---
<!-- This next part will show your top three most recent posts. You can change how readableDate looks in your .eleventy.js file-->
## Recent Blog Posts
<div id="recentPostList">
<ul>
{% assign top_posts = collections.posts | reverse %}
{%- for post in top_posts limit:3 -%}
<li><a href="{{ post.url }}">{{ post.date | readableDate }} » {{ post.data.title }}</a></li>
{% endfor %}
<li class="moreposts"><a href="archives.html">» Archives</a></li>
<li class="moreposts"><a href="rss.xml">» RSS feed</a></li></ul>
</div>
Let’s go over what these different parts are.
Frontmatter
Everything between the top two ---
lines is the frontmatter that Eleventy will use to populate variables in the future. You can set your files up any way you like. This is the system that I use for top-level pages:
- layout: This tells Eleventy to use the
base.njk
layout to make this page. We’ll go over thebase.njk
in a bit. - title and description: Self-explanatory. You don’t strictly need them, but I will use these fields to populate the page metadata later.
- featured_image: Again, this is mostly for page metadata purposes.
There is also the tags frontmatter field for our blog entries, but we’ll get to that later.
The content
You can write the content of your posts and pages in Markdown or HTML or a mixture of both, as I have done.
You’ll also see that I have added a block for Recent Blog Posts which will:
- Grab everything tagged “posts” (as you will see later, ALL blog posts will by default be tagged “posts”)
- Show them in reverse chronological order (i.e. most recent first)
- Limit this list to three items
Create base.njk
Next, we’re going to create the template that your top-level pages will use. Go into your /src/_includes/
directory and create a new file named base.njk
. Open it in your editor and throw this code in there:
/my cool blog/src/_includes/base.njk
---
title: "My Cool Blog: {{ title }}"
---
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://yourwebsitehere.com/rss.xml" rel="alternate" type="application/rss+xml" title="RSS feed for My Cool Blog">
<title>My Cool Blog: {{ title }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="This is the description for My Cool Blog!">
<link rel="icon" href="images/favicon.png" type="image/x-icon">
<link href="styles/style.css" rel="stylesheet" type="text/css" media="all">
<meta property="og:type" content="website">
<meta property="og:title" content="{{ title }}">
<meta property="og:url" content="https://yourwebsitehere.com/{{ permalink }}">
<meta property="og:image" content="https://yourwebsitehere.com/images/{{ featured_image }}">
<meta property="og:description" content="{{ description }}">
</head>
<body>
<main id="container">
<div id="headerArea">
<!--If you wanted to put in a header image, this is where you would do it-->
<nav id="navbar">
<!--This is where you edit the navbar links at the top of the page-->
<ul>
<li><a href="index.html">Home</a></li>
<li><a href="archives.html">Archives</a></li>
<li><a href="links.html">Links</a></li>
</ul>
</nav>
</div>
<!--This is where the bulk of your page lives-->
<div id="flex">
<article>
<h1>{{ title }}</h1>
<!--The content of your page-->
{{ content | safe }}
</article>
</div>
<!--Edit in your footer here, or delete it entirely if you want-->
<footer id="footer">My Cool Blog is really cool, right?</footer>
</main>
<!--this will add a 'Go to top' button on your site-->
<button onclick="topFunction()" id="topBtn" title="Go to top">Top</button>
<script>
let topbutton = document.getElementById("topBtn");
window.onscroll = function() {scrollFunction()};
function scrollFunction() {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
topbutton.style.display = "block";
} else {
topbutton.style.display = "none";
}
}
function topFunction() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
</script>
<!--end top button code-->
</body>
</html>
A lot of this should be pretty familiar if you’re migrating your site over from Zonelets, or if you’ve already got a blog going. You’ll also see where the template will pull the {{ title }}, {{ description }}, {{ featured_image }}, and so on from the frontmatter of the index.md
file we made earlier.
Creating your other top-level pages
In the sample base.njk
above, you’ll see that the navbar also has links to archives.html
and a links.html
. So in your /src/
directory, let’s make quick placeholders for them like so:
/my cool blog/src/archives.md
---
layout: base.njk
permalink: "{{ page.fileSlug }}.html"
title: Archives
description: These are where old posts go!
featured_image: favicon.png
---
## Tag Cloud
<!--This next part shows links to all the tags you have on your posts, ordered by frequency-->
<p class="center">{% for tag in collections -%}{% if tag[0] != "all" and tag[0] != "posts" %}<a href="../tags/{{ tag[0] | slugify }}.html">{{ tag[0] }}</a> ({{ tag | length }}) {% endif %}{%- endfor %}</p>
---
## All Posts
<!--This next part shows all of your posts tagged "posts" in reverse chronological order-->
<ul class="none">
{% assign top_posts = collections.posts | reverse %}
{%- for post in top_posts -%}
<li><a href="{{ post.url }}">{{ post.date | readableDate }} » {{ post.data.title }}</a></li>
{% endfor %}
</ul>
(BIG THANKS to Claus for the help with the tag cloud!)
Just like with the Recent Blog Posts block from index.md
, this page has a block that will bring up all posts tagged “posts” and display them in reverse chronological order. However, here they are not limited to three, and will list all of them.
Also notice how in the frontmatter, we have added a new field for permalink. This will force Eleventy to export this as /archives.html
and not /archives/index.html
.
/my cool blog/src/links.md
---
layout: base.njk
permalink: "{{ page.fileSlug }}.html"
title: Links
description: More cool sites!
featured_image: favicon.png
---
Believe it or not, this is not the only cool site on the Interwebs!
- [Wikipedia](https://www.wikipedia.org/)
- [Eleventy](https://11ty.dev)
- [W3C Markup Validation Service](https://validator.w3.org/)
Obviously, you don’t need to make a Links page, you can actually make this whatever you want. Just make sure to edit the navbar URL in the base.njk
template so that it points to the right place. Notice also how the layout and permalink fields are the same as archives.md
above.
Set up your posts directory and post.njk
Write your first blog post
Back in your /src/
directory, create a new folder and name it /posts/
. Open that directory and create a new .md file. Preface the filename with the date and title like YYYY-MM-DD-Title
, for example 2025-07-25-Hello-World.md
. Open it in your editor and edit it with something like this:
/my cool blog/src/posts/2025-07-25-Hello-World.md
---
layout: post.njk
permalink: "posts/{{ page.date | date: '%Y-%m-%d' }}-{{ page.fileSlug }}.html"
title: Hello, World!
description: This is the first post on my brand new cool blog!
featured_image: cat.jpg
tags:
- journal
---
This is my first blog post using [Eleventy](https://www.11ty.dev/)!

Some quick notes:
- Notice that the layout field in the frontmatter is now pointing to
post.njk
. We’ll be making that in a sec. - Also notice that the I prefaced my permalink with
posts/
which means that Eleventy will create a directory called “posts” and then make this file2025-07-25-Hello-World.html
inside of that directory. - You can actually set the date to whenever you want by changing the filename. This is handy for importing old posts or manually setting the date for your posts.
- You can add however many tags you want, and that will add this post to those collections. More on that later.
Create posts.json
Create a file named posts.json
inside of your /src/posts/
folder. Open it in your editor and add this:
/my cool blog/src/posts/posts.json
{
"layout": "post",
"tags": "posts"
}
This tells Eleventy that every page that uses the post.njk
layout should also be tagged “posts.” Remember how the Recent Blog Posts section in index.md
and the All Archives section in archive.md
was referring to collections.posts
? That means it will pull everything using this “posts” tag, i.e. all your blog entries but not top-level pages.
Create post.njk
Open your /src/_includes/
directory and create a new file called post.njk
. As you may have guessed, this will be your blog post template.
/my cool blog/src/_includes/post.njk
---
title: "My Cool Blog: {{ title }}"
---
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://yourwebsitehere.com/rss.xml" rel="alternate" type="application/rss+xml" title="RSS feed for My Cool Blog">
<title>My Cool Blog: {{ title }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="This is the description for My Cool Blog!">
<link rel="icon" href="../images/favicon.png" type="image/x-icon">
<link href="../styles/style.css" rel="stylesheet" type="text/css" media="all">
<meta property="og:type" content="article">
<meta property="og:title" content="{{ title }}">
<meta property="og:url" content="https://yourwebsitehere.com/{{ permalink }}">
<meta property="og:image" content="https://yourwebsitehere.com/images/{{ featured_image }}">
<meta property="og:description" content="{{ description }}">
</head>
<body>
<main id="container">
<div id="headerArea">
<!--If you wanted to put in a header image, this is where you would do it-->
<nav id="navbar">
<!--This is where you edit the navbar links at the top of the page-->
<ul>
<li><a href="../index.html">Home</a></li>
<li><a href="../archives.html">Archives</a></li>
<li><a href="../links.html">Links</a></li>
</ul>
</nav>
</div>
<!--This is where the bulk of your page lives-->
<div id="flex">
<article>
<h1>{{ title }}</h1>
<div class="postMetadata">
<!--You can change how topDate looks in your .eleventy.js file-->
<strong>{{ page.date | topDate }}</strong> //
<!--This will list the tags on your post EXCEPT for the "post" tag, separated by commas-->
{% set comma = joiner() %}
{% for tag in tags -%}
{% if tag !== 'posts' %}<a href="../tags/{{ tag | slugify }}.html">{{ tag }}</a>{%- if not loop.last %}, {% endif %}
{% endif %}
{%- endfor %}</div>
<!--The content of your post-->
{{ content | safe }}
<!--This will take you to the next/previous blog entries if they exist-->
<nav id="nextprev">
{% if nextPost.url %}
<a class="next" href="{{ nextPost.url }}">« Next</a> |
{% endif %}
<a href="../archives.html">Archives</a>
{% if prevPost.url %}
| <a class="previous" href="{{ prevPost.url }}">Previous »</a>
{% endif %}
</nav>
</article>
</div>
<!--Edit in your footer here, or delete it entirely if you want-->
<footer id="footer">My Cool Blog is really cool, right?</footer>
</main>
<!--this will add a 'Go to top' button on your site-->
<button onclick="topFunction()" id="topBtn" title="Go to top">Top</button>
<script>
let topbutton = document.getElementById("topBtn");
window.onscroll = function() {scrollFunction()};
function scrollFunction() {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
topbutton.style.display = "block";
} else {
topbutton.style.display = "none";
}
}
function topFunction() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
</script>
<!--end top button code-->
</body>
</html>
Again, you can see where all of the {{ title }} and other frontmatter variables fit in here. You can also see the next/previous pagination links at the bottom of the page, with the variables we set up in .eleventy.js.
Set up Tag pages
Create tag.njk
Something that is very cool about Eleventy is that you can set it up to automatically create pages for you based on your posts’ metadata, such as Tag pages. Open your /src/
directory and create a new file called tag.njk
.
/my cool blog/src/tag.njk
---
title: {{ tag }}
layout: tag_pages.njk
pagination:
data: collections
size: 1
alias: tag
filter:
- all
- posts
permalink: "/tags/{{ tag | slugify }}.html"
---
<!--Displays the posts that have this tag-->
<ul>
{% set taglist = collections[ tag ] %}
{% for post in taglist | reverse %}
<li class="archives"><a href="{{ post.url }}">{{ post.data.title }}</a> <em>{{ post.date | readableDate }}</em> » {{ post.data.description }}</li>
{% endfor %}
</ul>
In the frontmatter, we can see that this will pull data from collections (essentially our site’s tags) one at a time (i.e. one tag per tag page). It will do this for all of our collections EXCEPT “posts” and “all,” which we have filtered out. And in the permalink field, we can see that tag pages will be placed into the folder /tags/
. You can change this to whatever you want (or delete it entirely if you want your tag pages to live in your top-level directory).
You can also see that tag pages pull use the tag_pages.njk
layout, so let’s make that too…
Create tag_pages.njk template
In your /src/_includes/
directory, create a new file called tag_pages.njk
. This will be the template of your tag pages.
/my cool blog/src/_includes/tag_pages.njk
---
title: "My Cool Blog: {{ tag }}"
---
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://yourwebsitehere.com/rss.xml" rel="alternate" type="application/rss+xml" title="RSS feed for My Cool Blog">
<title>My Cool Blog: {{ tag }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="This is the description for My Cool Blog!">
<link rel="icon" href="../images/favicon.png" type="image/x-icon">
<link href="../styles/style.css" rel="stylesheet" type="text/css" media="all">
<meta property="og:type" content="article">
<meta property="og:title" content="{{ tag }}">
<meta property="og:url" content="https://yourwebsitehere.com/tags/{{ tag | slugify }}.html">
<meta property="og:image" content="https://yourwebsitehere.com/images/favicon.png">
<meta property="og:description" content="Blog posts tagged “{{ tag }}”">
</head>
<body>
<main id="container">
<div id="headerArea">
<!--If you wanted to put in a header image, this is where you would do it-->
<nav id="navbar">
<!--This is where you edit the navbar links at the top of the page-->
<ul>
<li><a href="../index.html">Home</a></li>
<li><a href="../archives.html">Archives</a></li>
<li><a href="../links.html">Links</a></li>
</ul>
</nav>
</div>
<!--This is where the bulk of your page lives-->
<div id="flex">
<article>
<h1>Tagged “{{ tag }}”</h1>
<!--The content of "tag.md" i.e. list of pages in this tag-->
{{ content | safe }}
</article>
</div>
<!--Edit in your footer here, or delete it entirely if you want-->
<footer id="footer">My Cool Blog is really cool, right?</footer>
</main>
<!--this will add a 'Go to top' button on your site-->
<button onclick="topFunction()" id="topBtn" title="Go to top">Top</button>
<script>
let topbutton = document.getElementById("topBtn");
window.onscroll = function() {scrollFunction()};
function scrollFunction() {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
topbutton.style.display = "block";
} else {
topbutton.style.display = "none";
}
}
function topFunction() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
</script>
<!--end top button code-->
</body>
</html>
Set up your RSS feed
Go back to your /src/
folder and create a new file named rss.njk
. Edit it to look like:
/my cool blog/src/rss.njk
---json
{
"permalink": "rss.xml",
"eleventyExcludeFromCollections": true,
"metadata": {
"title": "My Cool Blog",
"subtitle": "This is the description for My Cool Blog!",
"url": "https://yourwebsitehere.com/",
"feedUrl": "https://yourwebsitehere.com/rss.xml",
"author": {
"name": "Dennis the Menace"
}
}
}
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="{{ metadata.url }}">
<title>{{ metadata.title }}</title>
<subtitle>{{ metadata.subtitle }}</subtitle>
<link href="{{ permalink | absoluteUrl(metadata.url) }}" rel="self"/>
<updated>{{ collections.posts | getNewestCollectionItemDate | dateToRfc3339 }}</updated>
<id>{{ metadata.url }}</id>
<author>
<name>{{ metadata.author.name }}</name>
</author>
{%- for post in collections.posts | head(-15) | reverse %}
{%- set absolutePostUrl = post.url | absoluteUrl(metadata.url) %}
<entry>
<title>{{ post.data.title }}</title>
<link href="{{ absolutePostUrl }}"/>
<updated>{{ post.date | dateToRfc3339 }}</updated>
<id>{{ absolutePostUrl }}</id>
<content xml:lang="{{ metadata.language }}" type="html"><![CDATA[<img src="https://yourwebsitehere.com/images/{{ post.data.featured_image }}">]]>{{ post.data.description }}</content>
</entry>
{%- endfor %}
</feed>
Again, you can see this is where stuff like {{ featured_image }} and {{ description }} all get automated. And you can see that only pages tagged “post” (i.e. all of your blog entries but none of your top pages) will show up here, limited to the 15 most recent entries.
Set up your style.css
In your /src/styles/
directory, create style.css
if you don’t already have one. Editing your site’s CSS and layout is beyond the scope of this tutorial, so for now, here is a quick sample CSS that I made for Daikon’s (now-defunct) blog, and is based off the one that comes with Zonelets:
/my cool blog/src/styles/style.css
body {
background-color: #001D4A;
background-image: url("../images/bg.gif");
font-size: 1.2em;
font-family: "Courier New", sans-serif;
margin: 0;
color: #EEE5E5;
line-height: 1.6em;
}
a, a:visited {
color: #ECA400;
text-decoration: underline;
}
a:hover {
text-decoration: underline;
background-color: #006992;
}
* {
box-sizing: border-box;
}
#container {
max-width: 700px;
margin: 3em auto;
width: 90%;
}
#navbar {
background-color: #001D4A;
width: 100%;
padding: 0 5%;
font-weight: bold;
margin-bottom: 10px;
outline-width: 4px;
outline-style: solid;
outline-color: #006992;
}
#navbar ul {
display: flex;
padding: 0;
margin: 0;
list-style-type: none;
justify-content: space-evenly;
}
#navbar li {
padding-top: 5px;
padding-bottom: 5px;
font-size: 1.2em;
display: inline-block;
margin-right: 1.5em;
margin-bottom: 0.2em;
margin-top: 0.2em;
}
#navbar li a, #navbar li a:visited {
color: #ECA400;
text-decoration: none;
padding: 3px;
}
#navbar li a:hover {
background-color: #006992;
color: #ECA400;
text-decoration: none;
}
#flex {
display: flex;
}
article {
padding: 10px 5% 20px 5%;
background-color: #001D4A;
outline-style: none;
margin-bottom: 10px;
width: 100%;
outline-width: 4px;
outline-style: solid;
outline-color: #006992;
}
footer {
background-color: #001D4A;
width: 100%;
padding: 10px;
text-align: center;
outline-style: none;
outline-width: 4px;
outline-style: solid;
outline-color: #006992;
}
h1, h2, h3, h4 {
color: #ECA400;
}
hr {
border: solid #ECA400;
border-width: 1px 0 0 0;
}
blockquote {
padding: 0 20px;
margin-left: 0;
border-left: 2px dotted #006992;
font-size: 1.1em;
line-height: 1.6em;
}
img {
max-width: 100%;
height: auto;
margin-top: 0.5em;
margin-bottom: 0.5em;
}
ul.none {
padding: 0;
list-style-type: none;
}
/* Alignment classes */
.right {
float: right;
margin-left: 1em;
}
.left {
float: left;
margin-right: 1em;
}
.center {
display: block;
margin-right: auto;
margin-left: auto;
text-align: center;
}
/* Recent Blog Posts block in index.html */
#recentPostList ul {
padding: 0;
list-style-type: none;
}
/* Date/tags bar at the top of blog posts */
.postMetadata {
background-color: #001D4A;
width: 100%;
padding: 5px;
text-align: center;
outline-width: 1px;
outline-style: solid;
outline-color: #BBCFEA;
border-radius: 10px;
}
/* At the bottom of blog posts */
#nextprev {
text-align: center;
margin-top: 1.4em;
}
@media only screen and (max-width: 900px) {
#flex {
flex-wrap: wrap;
}
#navbar ul {
flex-wrap: wrap;
}
}
/* Go To Top button */
#topBtn {
display: none;
position: fixed;
bottom: 20px;
right: 30px;
z-index: 99;
border: none;
outline-width: 4px;
outline-style: solid;
outline-color: #006992;
background-color: #001D4A;
color: #ECA400;
cursor: pointer;
padding: 15px;
font-size: 18px;
font-family: "Courier New", sans-serif;
}
#topBtn:hover {
color: #ECA400;
background-color: #006992;
}
Export your site
IT’S TIME!! Go to your project root folder, right-click, and select Open in Terminal again. Then type in:
npx @11ty/eleventy --serve
Open your browser and go to that localhost address to see your site! You can edit the files, save them, and see them updated in real-time! You can also find your exported site files within the /my cool blog/public/
folder, which contains all the files you will need to upload somewhere like Neocities or Nearly Free Speech. (If you just want to export your files without starting the server, you can use npx @11/eleventy
without the --serve
.)
/my cool blog/public/index.html
/my cool blog/public/posts/2025-07-25-Hello-World.html
/my cool blog/public/tags/journal.html
/my cool blog/public/archives.html
/my cool blog/public/posts/links.html
/my cool blog/public/rss.xml
Adding new posts
Duplicate the “Hello, World!” blog entry file in the /src/posts/
directory and name it something like 2025-07-25-Mario-Review.md
. Let’s change the “journal” tag to “video games” in the frontmatter. Notice how you do not need to edit the layout or permalink fields.
/my cool blog/src/posts/2025-07-25-Mario-Review.md
---
layout: post.njk
permalink: "posts/{{ page.date | date: '%Y-%m-%d' }}-{{ page.fileSlug }}.html"
title: Mario Review
description: This is a blog review for that cool Mario video game!
featured_image: mario.jpg
tags:
- video games
---
Mario is so much fun, amirite?

Open up your terminal again and do npx @11/eleventy
to export, and to go your /public/
folder to find:
/my cool blog/public/posts/2025-07-25-Mario-Review.html
Like magic, you should now have a new “video games” tag on your site! Your Archives page should now look like:
/my cool blog/public/archives.html
There is also a new tag page for “video games”:
/my cool blog/public/tags/video-games.html
You can also see that your posts have been automatically updated with next/previous pagination links, and the Recent Blog Posts in your index.html
and RSS have also been updated with your new post:
/my cool blog/public/posts/2025-07-25-Hello-World.html
/my cool blog/public/index.html
/my cool blog/public/rss.xml
Go forth and make cool sites
This has been only a sample of cool stuff you can do with Eleventy! If you haven’t yet, I highly recommend checking out the documentation, as well as seeing what other people have been doing. I just wanted to show you how you can automate some of the process of running a fairly simple blog while still maintaining that scrappy sense of Web 1.0 DIY coding. If you make something cool, please tell me about it and I’ll add your site to my links page!