Trung Pham Duy - November 2023
HTML, Markdown, WebC, JavaScript, Nunjucks, Liquid, Handlebars, Mustache, EJS, HAML, Pug, Custom
/ -> Home page
/about -> About page
/blog -> List of posts page
/blog/:id -> A single post page
In terminal:
mkdir 11ty-blog && cd 11ty-blog
npm init -y
npm install @11ty/eleventy
Add scripts to package.json:
{
"scripts": {
"build": "npx @11ty/eleventy",
"dev": "npx @11ty/eleventy --serve"
}
}
At project root, add index.md:
# My personal corner
Powered by 11ty.
In terminal, npm run build. Output is folder _site. In _site/index.html:
<h1>My personal corner</h1>
<p>Powered by 11ty</p>
At project root, add about/index.md:
# About me
I love __markdown__!.
> This is a quote I said
Then build. Output:
_site
│ index.html
└───about
index.html
In terminal:
git init
At project root, add a .gitignore file:
node_modules
_site
Then commit.
At project root, add _includes/layout.njk (Nunjucks):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/sakura.css/css/sakura.css" type="text/css">
<!-- Grab title from the page data and dump it here -->
<title>{{ title }}</title>
</head>
<body>
<!-- Grab the content from the page data, dump it here, and mark it as safe -->
<!-- Safe docs: https://mozilla.github.io/nunjucks/templating.html#safe -->
{{ content | safe }}
</body>
</html>
Add Frontmatter to the top of markdown files.
# index.md
---
layout: layout.njk
title: Homepage | My Blog
---
Now npm run dev. Dev server hosted at http://localhost:8080/.
Add blog/what-is-a-cdn.md:
---
layout: layout.njk
title: What is a CDN
tags: ['blog', 'cdn']
---
# What is a CDN
[Source](https://www.cloudflare.com/learning/cdn/what-is-a-cdn/)
A content delivery network (CDN) is a geographically distributed group of servers that caches content close to end users.
blog postsAdd blog/index.njk:
---
layout: layout.njk
---
<h1>My blog</h1>
<ul>
{% for post in collections.blog %}
<li><a href="{{ post.url }}">{{ post.data.title }}</a></li>
{% endfor %}
</ul>
At _includes/layout.njk, after <body>:
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
</nav>
</header>
Add blog/webdev/css-intro.md:
---
layout: layout.njk
title: "CSS: Cascading Style Sheets"
tags: ['blog', 'css']
---
# CSS: Cascading Style Sheets
[Source](https://developer.mozilla.org/en-US/docs/Web/CSS)
Cascading Style Sheets (CSS) is a stylesheet language used to describe the presentation of a document written in HTML or XML (including XML dialects such as SVG, MathML or XHTML).
CSS describes how elements should be rendered on screen, on paper, in speech, or on other media.
At root, create css/styles.css:
nav > ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
gap: 1rem;
}
Add to _includes/layout.njk’s <head>
<link rel="stylesheet" href="/css/styles.css" type="text/css">
At root, add .eleventy.js:
module.exports = function (eleventyConfig) {
eleventyConfig.addPassthroughCopy('css/styles.css');
return {
passthroughFileCopy: true,
};
};
Restart dev server.
Metadata normally at the top of template files.
---
title: My page title
---
The above is using YAML syntax.
As 11ty uses gray-matter to parse Front Matter, use can use other formats like json and JS Objects as well.
---json
{
"title": "My page title"
}
---
<!doctype html>
<html>
...
---js
{
title: "My page title",
currentDate: function() {
// You can have a JavaScript function here!
return (new Date()).toLocaleString();
}
}
---
<h1>{{ title }}</h1>
<p>Published on {{ currentDate() }}</p>
permalinkpermalink: Change the output target of the current template.# Map to new path
permalink: "this-is-a-new-path/subdirectory/testing/index.html"
# skip writing file
permalink: false
# with template syntax
permalink: "subdir/{{ title | slugify }}/index.html"
layoutlayout: Wrap current template with a layout template found in the _includes folder.# _includes/layout.njk
layout: layout.njk
# _includes/default/layout.njk
layout: default/layout.njk
paginationpagination: Enable to iterate over data. Output multiple HTML files from a single template.tagstags: A single string or array that identifies that a piece of content is part of a collection. Collections can be reused in any other template.datedate: Override the default date (file creation) to customize how the file is sorted in a collection.eleventyComputedeleventyComputed: Programmatically set data values based on other values in your data cascade.Global data is exposed to every template in an Eleventy project, can be either:
. by default), and the name of the global data folder is dir.data configuration option (_data by default).*.json and module.exports values from *.js files in this folder will be added into a global data object available to all templates.Create _data/header.json:
[
{
"url": "/",
"label": "Home"
},
{
"url": "/about",
"label": "About"
},
{
"url": "/blog",
"label": "Blog"
}
]
In _includes/layout.njk, replace header>nav>ul’s content with:
{% for item in header %}
<li><a href="{{item.url}}">{{item.label}}</a></li>
{% endfor %}
As you can see, header magically appears as global variable inside template files.
In .eleventy.js:
module.exports = function (eleventyConfig) {
//...other code
eleventyConfig.addGlobalData("header", [
{
"url": "/",
"label": "Home"
},
{
"url": "/about",
"label": "About"
},
{
"url": "/blog",
"label": "Blog"
}
]);
//...other code
};
Global data can come from other services. Add _data/posts.js.
npm install ofetch
const { ofetch } = require('ofetch');
module.exports = async function () {
const url = 'https://jsonplaceholder.typicode.com/posts';
return ofetch(url);
};
Of course, you can use any HTTP client: axios, ky, node-fetch…
Add posts/index.njk.
---
title: Homepage | My blog
layout: layout.njk
---
<h1>My blog</h1>
<p>
Powered by 11ty.
</p>
<ul>
{% for post in posts %}
<li>
{{ post.title }}
</li>
{% endfor %}
</ul>
In dev, every time you hit save, API is called 😭.

API quota will run out soon 💀.

Plugins are custom code that Eleventy can import into a project from an external repository.
npm install @11ty/eleventy-fetch
In _data/posts.js.
const EleventyFetch = require('@11ty/eleventy-fetch');
module.exports = async function () {
const url = 'https://jsonplaceholder.typicode.com/posts';
return EleventyFetch(url, {
duration: '1m', // save for 1 minute
type: 'json', // we’ll parse JSON for you
});
};
API is only called when cache is stale (> 1 minute).

Each page with size=5. At posts/index.njk.
---
title: Posts
layout: layout.njk
pagination:
data: posts
size: 5
permalink: "/posts/{{ pagination.pageNumber }}/index.html"
---
<h1>Posts</h1>
<ul>
{% for item in pagination.items %}
<li>
<a href="/posts/{{ item.title | slugify }}">
{{ item.title }}
</a>
</li>
{% endfor %}
</ul>
pagination.pageNumberstarts from 0.
<nav aria-labelledby="my-pagination">
<ul>
<li>{% if page.url != pagination.href.first %}<a href="{{ pagination.href.first }}">First</a>{% else %}First{% endif %}</li>
<li>{% if pagination.href.previous %}<a href="{{ pagination.href.previous }}">Previous</a>{% else %}Previous{% endif %}</li>
{%- for pageEntry in pagination.pages %}
<li><a href="{{ pagination.hrefs[ loop.index0 ] }}"{% if page.url == pagination.hrefs[ loop.index0 ] %} aria-current="page"{% endif %}>{{ loop.index }}</a></li>
{%- endfor %}
<li>{% if pagination.href.next %}<a href="{{ pagination.href.next }}">Next</a>{% else %}Next{% endif %}</li>
<li>{% if page.url != pagination.href.last %}<a href="{{ pagination.href.last }}">Last</a>{% else %}Last{% endif %}</li>
</ul>
</nav>
Change permalink.
permalink: "/posts/{% if pagination.pageNumber > 0 %}page-{{ pagination.pageNumber + 1 }}/{% endif %}index.html"
Create posts/$slug.njk (file name is not important, file extension is important).
---
pagination:
data: posts
size: 1
alias: post
permalink: "posts/{{ post.title | slugify }}/"
eleventyComputed:
title: "{{ post.title }}"
layout: layout.njk
---
<h1>{{ title }}</h1>
{{ post.body }}
pkg: project’s package.json values.pagination: when enabled using pagination in front matter.collections: Lists of all of your content, grouped by tags.page: Has information about the current page.eleventy: contains Eleventy-specific data from environment variables and the Serverless plugin (if used).includeinclude pulls in other templates in place. It’s useful when you need to share smaller chunks across several templates that already inherit other templates._includes folder.header partialCreate _includes/partials/header.njk:
<header>
<nav>
<ul>
{% for item in header %}
<li>
<a href="{{ item.url }}">{{ item.label }}</a>
</li>
{% endfor %}
</ul>
</nav>
</header>
header partial (cont)In _includes/layout.njk:
...
<body>
{% include "partials/header.njk" %}}
<!-- Grab the content from the page data, dump it here, and mark it as safe -->
<!-- Safe docs: https://mozilla.github.io/nunjucks/templating.html#safe -->
{{ content | safe }}
</body>
...
tags key in the front matter.tags have a singular purpose in Eleventy: to construct collections of content.tags---
# single tag
tags: cat
# access in template: `collections.cat`
# multiple words in a single tag
tags: cat and dog
# access in template: `collections['cat and dog']`
# multiple tags, single line
tags: ['cat', 'dog']
# or multiple tags, multiple lines
tags:
- cat
- dog
# content would show up in: `collections.cat`, `collections.dog`
---
all CollectionBy default Eleventy puts all of your content (independent of whether or not it has any assigned tags) into the collections.all Collection.
This allows you to iterate over all of your content inside of a template.
---
eleventyExcludeFromCollections: true
tags: post
---
This will not be available in collections.all or collections.post.
{
page: {
inputPath: './test1.md',
url: '/test1/',
date: new Date(),
// … and everything else in Eleventy’s `page`
},
data: { title: 'Test Title', tags: ['tag1', 'tag2'], date: 'Last Modified', /* … */ },
content: '<h1>This is my title</h1>\n\n<p>This is content…'
}
<ul>
{%- for post in collections.post -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
The default collection sorting algorithm sorts in ascending order using:
<!-- nunjucks -->
<ul>
{%- for post in collections.post | reverse -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
module.exports = function(eleventyConfig) {
// Filter source file names using a glob
eleventyConfig.addCollection("posts", function(collectionApi) {
return collectionApi.getFilteredByGlob("_posts/*.md");
});
};
Consider a template located at posts/subdir/my-first-blog-post.md.
Eleventy will look for data in the following places (starting with highest priority, local data keys override global data):
1. Content Template Front Matter Data
- merged with any Layout Front Matter Data
2. Template Data File
posts/subdir/my-first-blog-post.11tydata.js
posts/subdir/my-first-blog-post.11tydata.json
posts/subdir/my-first-blog-post.json
3. Directory Data File
posts/subdir/subdir.11tydata.js
posts/subdir/subdir.11tydata.json
posts/subdir/subdir.json
4. Parent Directory Data File
posts/posts.11tydata.js
posts/posts.11tydata.json
posts/posts.json
5. Global Data Files in _data/* (.js or .json files) available to all templates.
layout and tags for BlogCreate blog/blog.json.
{
"layout": "layout.njk",
"tags": "blog"
}
Choose a repo, clone, then customize and add content.