11ty + Nunjucks

25-02-2021 / Edit on Github

This is not a tutorial to create an 11ty website, this's a note! You can find some very new and useful techniques on this note alongside the official documentation.

This note will be always updated!

Installation #

πŸ‘‰ First, install nodejs.
πŸ‘‰ Using this starter template or Google's high performance statrer theme (recommended).

# Install dependencies
npm install

Depend on each theme, you should follow the installation steps given in that theme.

Setting up with Netlify #

Sometimes, 11ty takes too much time to build (especially on the task of optimizing images. On my site, it takes almost 10 minutes). You shouldn't use branch master to build you site because every time you make a push, Netlify will rebuild your whole site. You should create and use a new branch, so-called prod instead.

Idea 1 -- manually build but should not use many times

On Netlify, go to Site settings > Build & deploy:

  • Build settings:
    • Build command: npm run build (depends on your site)
    • Publish directory: _site (depends on your site)
    • Builds: Active builds
  • Deploy contexts:
    • Production branch: prod (the same as the your created branch)
    • Deploy previews: Don't deploy pull requests (you don't want someone pull request and it auto make a deploy)
    • Branch deploys: Deploy only the production branch.

The weakness of Idea 1 is that you let netlify build again your whole site with its resources. That's why it takes too much time! Remember that, you have only 300 free minutes to build.

Idea 2 -- build locally and push _site only

You should know that, even if your site contains only html files, netlify is able to make it live as usual.

  1. Working mainly on branch dev like in Idea 1.

  2. Create a branch, so-called _site from dev. In this branch, delete all folders except _site, node_modules, .git, .gitignore.

  3. Modify .gitignore (exclude all except _site folder to push to github),

    /*
    /*/
    !/_site/
  4. Now, we tell netlify build our site from branch _site (which contains html files only so it doesn't take time to build anything, it's really fast!)

    • Build settings:
      • Base directory: Not set.
      • Build command: Not set.
      • Publish directory: _site/
      • Builds: Active.
    • Deploy context:
      • Production branch: site
      • Deploy previews: Don’t deploy pull requests
      • Branch deploys: Deploy only the production branch
Example workflow with dinhanhthi.com

From the main repo, I clone to 2 different folders

|- dinhanhthi.com # <- branch "dev"
|- dat.com # <- branch "_site"

On dat.com, I create a script called ud_site.sh which contains,

echo "Start building site"
cd ../dinhanhthi.com/
npm run build
cd ../dat.com/
echo "Start copying...."
cp -Rf ../dinhanhthi.com/_site/* _site/
echo "End copying"
git add .
git commit -m "Updated: `date +'%Y-%m-%d %H:%M:%S'`"
git push

For covenience, I create an alias in bash shell,

alias update_dat='cd ~/git/dat.com && sh ud_site.sh && cd -1'

From now, whenever I wanna build and deploy my site on netlify, I just run,

update_dat

I saved from 1h of building to 2m of building on netlify with this method!

Templating #

SCSS to CSS #

If you use node-sass
# Folder's structure
css/main.scss
css/components/_fonts.scss # with _
css/components/....
// in main.scss
@import "./components/font"; // without extension

// package.json
{
"scripts": {
"js-build": "node-sass css/main.scss -o css",
}
}
<!-- in <head> -->
<link rel="stylesheet" href="css/main.css">
If you use rollup (like this site)
# Folder's structure
css/main.scss
css/components/_fonts.scss
css/components/....
css/main_input.js
// in main.scss
@import "./components/font"; // without extension

// package.json
{
"scripts": {
"js-build": "rollup -c rollup.config.js",
}
}
import scss from 'rollup-plugin-scss';

export default [
{
// plugin 1
},
{
input: 'css/main_input.js', // where the input file containing import of main.scss
output: {
file: 'css/main.js', // intermediate file which can be translated to css/main.css
format: 'esm' // still not know
},
plugins: [
scss() // there are other configs
]
}
];
<!-- in <head> -->
<link rel="stylesheet" href="css/main.css">
If you use parcel
# install
npm i parcel-bundler
npm i npm-run-all -D
# folder structure
_assets/css/main.scss
___________/_bootstrap.scss
_______/js/main.js
# main.scss
@import "./bootstrap";
# main.js
import "./../css/main.scss";
# package.json
"scripts": {
"start": "npm-run-all --parallel dev:*",
"build": "run-s prod:*",
"dev:eleventy": "eleventy --serve",
"dev:parcel": "parcel watch ./_assets/js/main.js --out-dir ./_site/assets",
"prod:eleventy": "eleventy",
"prod:parcel": "parcel build ./_assets/js/main.js --out-dir ./_site/assets",
},
# run
npm start

Nunjucks inside css #

<style>
.bg {
background-image: url();
}
</style>

<!-- or -->
<div style="background-image: url();"></div>

Bootstrap + 11ty #

πŸ‘‰ Bootstrap's homepage
πŸ‘‰ How to Isolate Bootstrap CSS to Avoid Conflicts (using LESS)

# install
npm i bootstrap jquery popper.js

Using alongside with section "SCSS to CSS".

# folder structure
_assets/css/main.scss
_______/vender/_bootstrap.scss
// main.scss
@import "./vender/bootstrap";
// _bootstrap.scss
// all components
// @import "./../../../node_modules/bootstrap/scss/bootstrap.scss";

// Required
// check more: https://getbootstrap.com/docs/4.5/getting-started/theming/#importing
@import "./../../../node_modules/bootstrap/scss/functions";
@import "./../../../node_modules/bootstrap/scss/variables";
@import "./../../../node_modules/bootstrap/scss/mixins";

Google Fonts #

  • Put fonts in fonts/ and use this tool to generate .woff, woff2 from Google Fonts. Be careful on the location will be used on your site.
  • Copy and paste to css/main.scss.
  • If you have a problem with Content-Security-Policy, check this section.

Layout #

mkdir _includes/layouts
touch _includes/layouts/post.njk
// create an alias
module.exports = function(eleventyConfig) {
eleventyConfig.addLayoutAlias('post', 'layouts/post.njk');
};
# update changes
touch .eleventy.js
# then use
---
layout: post
---

Includes #

Split layout into parts and include them in the main file.

// in _includes/components/head.njk
{% include "components/head.njk" %}

// custom parameter
{% set customClass = 'list-homepage' %}
{% include "postslist.njk" %}
// inside postlist.njk, just use {{ customClass }}

Template inheritance #

Read this tutorial.

<!-- _includes/layouts/base.njk -->
<body>
<header>

</header>
</body>
<!-- _includes/layouts/post.njk -->
---
---
{% extends "layouts/base.njk" %}

{% block headerLogo%}
<!-- only appear on post layout -->
{% endblock %}

Post's components #

πŸ‘‰ Page variable components.

// URL can be used in <a href> to link to other templates
url: "/current/page/myFile/",

// For permalinks: inputPath filename minus template file extension (New in v0.3.4)
fileSlug: "myFile",

// For permalinks: inputPath minus template file extension (New in v0.9.0)
filePathStem: "/current/page/myFile",

// JS Date Object for current page (used to sort collections)
date: new Date(),

// The path to the original source file for the template
// Note: this will include your input directory path!
inputPath: "./current/page/myFile.md",

// Depends on your output directory (the default is _site)
// You probably won’t use this: `url` is better.
outputPath: "./_site/current/page/myFile/index.html"

templateContent: //the rendered content of this template. This does not include layout wrappers.

data: // all data for this piece of content (includes any data inherited from layouts)
// self-defined frontmatter tags can be found here

For ones who wanna get only the content (escape HTML tags and special characters) of the post:

{{ page.templateContent | dump | safe | striptags(true) }}

Frontmatter #

---
title: Title of the post
description: description of the post
date: 2020-09-11
layout: layouts/post.njk
---
---
tags:
- default
# or
tags: [tag 1, tag 2]
---

List of post #

Normal,

<ul>
{% for post in collections.posts %}
<li>
<a href="{{ post.url | url }}">
{{ post.data.title }}
</a>
</li>
{% endfor %}
</ul>

Other default variable (like post.url) can be found here. Note that, you can use page.templateContent for the content of a page in some collections (not tested yet but you can try![ref]).

Sort posts by title #

<!-- Create a new list -->
{% set newPostList = [] %}
{% for post in collections.posts %}
{% set newPostList = (newPostList.push({title: post.data.title, url: post.url}), newPostList) %}
{% endfor %}

<!-- list of post by alphabet -->
<ul>
{% for post in newPostList|sort(attribute='title') %}
<li>
<a href="{{ post.url | url }}">
{{ post.title }}
</a>
</li>
{% endfor %}
</ul>

Posts by categories / tags #

In this case, we consider a category as the first tag of a post. For example, if a post has tags tags: [tag 1, tag 2, tag 3], then tag 1 will be its category!

// _data/categories.json
[
{
"name": "Cat 1",
"icon": "/img/cats/cat_1.svg"
},
{
"name": "Cat 2",
"icon": "/img/cats/cat_2.svg"
}
]
{% for item in categories %}
<div class="category-wrapper">
<h2>{{ item.icon }} {{ item.name }}</h2>
<ul class="post-list">
{% for post in collections[item.name] %}
<li class="post-item">
<a href="{{ post.url }}"><h3>{{ post.data.title }}</h3></a>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}

External posts #

If you wanna add external posts (not generated by 11ty from .md files), do below steps. For example, on this site, I cretae some pages created by Notion and I wanna add them to category MOOC.

Remark: This section is used with section "posts by categories" and section "sort posts by title".

// _data/cat_ex_posts.json
[
{
"title": "DL4 - Convolutional Neural Network (CNN)",
"url": "https://www.notion.so/dinhanhthi/CNN-by-deeplearning-ai-a081d253fc2c4c0b99edd2757c759b9e",
"tags": ["MOOC", "Deep Learning"]
},
{
"title": "DL5 - Sequence models",
"url": "https://www.notion.so/dinhanhthi/CNN-by-deeplearning-ai-a081d253fc2c4c0b99edd2757c759b9e",
"tags": ["MOOC", "Deep Learning"]
},
{
"title": "NLP by HSE",
"url": "https://www.notion.so/dinhanhthi/NLP-by-HSE-20cec3e92201475eb4d48787954f3aa4",
"tags": ["MOOC", "NLP"]
}
]
{% for item in categories %}
{% set postslist = collections[item.name] %}

{% set newPostListCat = [] %}
{% for post in cat_ex_posts %}
{% for tg in post.tags %}
{% if tg == item.name %}
{% set more_post = true %}
{% set newPostListCat = (newPostListCat.push({title: post.title, url: post.url, tags: post.tags}), newPostListCat) %}
{% endif %}
{% endfor %}
{% endfor %}

{% set newPostList = [] %}

{% for post in postslist %}
{% set newPostList = (newPostList.push({title: post.data.title, url: post.url}), newPostList) %}
{% endfor %}

{% if more_post %}
{% for post in newPostListCat %}
{% set newPostList = (newPostList.push({title: post.title, url: post.url, tags: post.tags}), newPostList) %}
{% endfor %}
{% endif %}

<div class="category-wrapper">
<h2>{{ item.icon }} {{ item.name }}</h2>
<ul class="post-list">
{% for post in newPostList %}
<li class="post-item">
<a href="{{ post.url }}"><h3>{{ post.title }}</h3></a>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}

Next / Previous post #

<ul>
{%- set nextPost = collections.posts | getNextCollectionItem(page) %}
{%- if nextPost %}<li>Next: <a href="{{ nextPost.url | url }}">{{ nextPost.data.title }}</a></li>{% endif %}
{%- set previousPost = collections.posts | getPreviousCollectionItem(page) %}
{%- if previousPost %}<li>Previous: <a href="{{ previousPost.url | url }}">{{ previousPost.data.title }}</a></li>{% endif %}
</ul>

Custom js scripts #

# Put scripts in
# /src/main.js
<!-- in <head> -->
<script async defer src="{{ '/js/min.js' | addHash }}"></script>

Using rollupjs,

// rollup.config.js
export default [
{
input: "src/main.js",
output: [
{
file: "js/min.js",
format: "iife",
sourcemap: true,
plugins: [terser()],
},
],
}
];

Last modified date #

// .eleventy.js
const { DateTime } = require("luxon");
module.exports = function (eleventyConfig) {
eleventyConfig.addFilter("readableDate", (dateObj) => {
return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat(
"dd LLL yyyy"
);
});

// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string
eleventyConfig.addFilter("htmlDateString", (dateObj) => {
return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat("dd-LL-yyyy");
});

eleventyConfig.addFilter("sitemapDateTimeString", (dateObj) => {
const dt = DateTime.fromJSDate(dateObj, { zone: "utc" });
if (!dt.isValid) {
return "";
}
return dt.toISO();
});
}

Last modified date,

{{ page.inputPath | lastModifiedDate  | htmlDateString }}

Insert code highlight #

Code syntax highlight: Need this plugin. List of supported languages.

# Highlight line 2
``` js/2
// lines of codes
```
# Highlight line 2 to 4
``` js/2-4
// lines of codes
```
# Highlight line 2, 4
``` js/2,4
// lines of codes
```
# Delete line 2 (red highlight)
# and add line 4 (green highlight)
``` js/4/2
// lines of codes
```

Insert liquid / nunjuck code #

Inline code, put {% raw %} and {% endraw %} around the keyword.

Code block,

~~~ js {% raw %}
// line of codes
{% endraw %}
~~~

Math equations #

KaTeX: Use markdown-it-katex

Using markdown-it-katex (use this version only),

// .eleventy.js
const markdownIt = require("markdown-it");
const markdownItKatex = require('@iktakahiro/markdown-it-katex');

let markdownLibrary = markdownIt()
markdownLibrary.use(markdownItKatex);
<!-- inside <head> -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/katex.min.css">

πŸ’‘ Some tips: working with KaTeX

# working
$$
\dfrac{1}{2}
$$
# not working
- Item

$$
\dfrac{1}{2}
$$
- Item
# working again
- Item

$$\dfrac{1}{2}$$ # without \n
- Item

In this site, I use markdown-it-texmath. I choose this because we can overcome the weakness of markdown-it-katex in case of breaking lines in list mode & it's more flexible (you can try it online here).

An important note: the original version has a problem of whitespace before and after <eq> tag in the inline mode. That why instead of seeing aaa x aaa with aaa $x$ aaa, we see aaaxaaa. I've changed (and reported an issue). For a moment, I use a modified version here.

# Install
# npm i markdown-it-texmath --save-dev # original, whitespace problem
npm i git+https://github.com/dinhanhthi/markdown-it-texmath.git --save-dev
// .eleventy.js
tm = require('markdown-it-texmath'),
md = require('markdown-it')().use(tm, { engine: require('katex'),
delimiters:'dollars',
katexOptions: { macros: {"\\RR": "\\mathbb{R}"} }
});
πŸ’‘ If you wanna modify yourself markdown-it-texmath

Copy the cloned folder markdown-it-temath (except .git, .gitignore, test/, package-lock.json) to a so-called /third-party/ folder in your project. In .eleventy.js, you import it as

const tm = require('./third_party/markdown-it-texmath');
<!-- Add in <head> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css">

<!-- Save to local and modify (or just use) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/markdown-it-texmath/css/texmath.min.css">

Mathjax: using markdown-it-mathjax

Figures #

# Insert normally,
![description](/path/to/image)
{% enraw %}
# With custom classes (using markdown-it-attrs)
![](){:.custom-class}

Markdown #

markdown-it & its plugins #

We use markdown-it and its plugins.

My choices of useful plugins

Just use npm i <plugin-name> --save-dev to install.

// .eleventy.js
const markdownIt = require("markdown-it");

module.exports = function (eleventyConfig) {
let markdownLibrary = markdownIt({
html: true, // html tag inside source
breaks: true, // use '\n' as <br>
linkify: true, // Autoconvert URL-like text to links
})
.use(require("markdown-it-anchor"), {
permalink: true,
permalinkClass: "direct-link",
permalinkSymbol: "#",
})
.use(require('markdown-it-mark')) // ==mark==
.use(require('markdown-it-attrs'), { // use {:} options
leftDelimiter: '{:',
rightDelimiter: '}'
})
.use(require("markdown-it-emoji")) // emoji
.use(require("markdown-it-table-of-contents")) // [[toc]] (no spaces)
.use(require('@iktakahiro/markdown-it-katex')) // katex
.use(require("markdown-it-task-lists")) // tasks [x]
.use(require('markdown-it-container'), 'success') // use `::: success` bock to create a custom div
.use(require('markdown-it-kbd')) // [[Ctrl]]
.use(require('markdown-it-footnote'))
.use(require('@gerhobbelt/markdown-it-inline-text-color'))
;
eleventyConfig.setLibrary("md", markdownLibrary);
}
How to use markdown-it's plugins in 11ty?

Below are an example of inserting 2 plugins,

// .eleventy.js
// An example of using plugins
const markdownIt = require("markdown-it");
var markdownItp = require('markdown-it')();

module.exports = function (eleventyConfig) {
let markdownLibrary = markdownIt({
html: true, // html tag inside source
breaks: true, // use '\n' as <br>
linkify: true, // Autoconvert URL-like text to links
})
.use(require("markdown-it-emoji")) // emoji
.use(require("markdown-it-table-of-contents")) // [[toc]] (no spaces)
;
eleventyConfig.setLibrary("md", markdownLibrary);
}

Custom container #

If you wanna create an advanced custom container, use plugin markdown-it-container. For example, you want export something like,

<div class="hsbox">
<div class="hs__title">
<!-- Custom Title -->
</div>
<div class="hs__content">
<!-- Custom markdown texts -->
</div>

Just by using,

::: hsbox Custom Title
Custom markdown texts
:::

You can put in .eleventy.js like,

.use(require('markdown-it-container'), 'hsbox', {
validate: function (params) {
return params.trim().match(/^hsbox\s+(.*)$/);
},
render: function (tokens, idx) {
var m = tokens[idx].info.trim().match(/^hsbox\s+(.*)$/);
if (tokens[idx].nesting === 1) {
// opening tag
return '<div class="hsbox"><div class="hs__title">'
+ markdownItp.renderInline(m[1])
+ '</div><div class="hs__content">';
} else {
// closing tag
return '</div></div>';
}
}
})

Markdown inside .njk #

// .eleventy.js

module.exports = function (eleventyConfig) {
eleventyConfig.addPairedShortcode("markdown", (content, inline = null) => {
return inline
? markdownLibrary.renderInline(content)
: markdownLibrary.render(content);
});
}
{% markdown %}
<!-- html tags -->
<!-- no need spaces before/after -->
{% endmarkdown %}

HTML/nunjucks tags inside .md #

<!-- not working -->
<div>
__abc__
</div>
<!-- working -->
<div>

__abc__
</div>
<!-- not working -->
<div class="list-of">

</div>
<!-- working -->
<div class="list-of">

</div>

Custom block shortcodes #

To creata the same code block like above, i.e.,

<div class="hsbox">
<div class="hs__title">
<!-- Custom Title -->
</div>
<div class="hs__content">
<!-- Custom markdown texts -->
</div>

Just by using,

{% hsbox "Custom Title" %}
<!-- Custom markdown texts -->
{% endhsbox %}
// .eleventy.js

module.exports = function (eleventyConfig) {
eleventyConfig.addPairedShortcode("hsbox", (content, title) => {
return '<div class="hsbox"><div class="hs__title">'
+ markdownLibrary.renderInline(title) + '</div><div class="hs__content">'
+ markdownLibrary.render(content) + '</div></div>';
});
}

Custom inline shortcodes #

If you wanna export something like,

<a href="custom-url">ref</a>

by using {% ref "custom-url" %} ("" is required). You can set,

// .eleventy.js

module.exports = function (eleventyConfig) {
eleventyConfig.addShortcode("ref", function(url) {
return '<sup><a href="'
+ url
+ '" rel="noopener noreferrer" target="_blank">[ref]</a></sup>';
});
}

For me, the best choice for search feature in 11ty is using Elasticlunr with some customizations.

Why not others?

Based on the purpose of free, quick, full text search:

  • We don't choose Google's Programmable Search because: it contains ads, not index as we want, difficult to customize with personal theme,...
  • We don't choose paid options like Agolia because the free option contains very few units. It's absolutely not enough for your need. In case you still want to use it with less consumption, read this article.

Because your site becomes bigger in future, you cannot index the whole text of your site (every time you build). My idea is to create a custom frontmatter tag called "keywords" which contains all of the important keywords used to determine the content of your posts. Of course, the cons is that you have to put the keywords manually!!

Check this repository, I've pulled and modified from this one (The author takes so long to check my pull request ^^). My customization supports:

  • Index your customizable keywords.
  • Fix UX bugs in the main repo.
  • Highlight found keywords in the search result.
  • Limit the max number of texts in the result (show around the found keywords).
  • Adapt to the newest version of 11ty.

Data files #

Local data files #

// .eleventy.js
module.exports = function (eleventyConfig) {

return {
dir: {
input: ".",
includes: "_includes",
data: "_data",
output: "_site",
},
}
}

You put all your data files (.js or .json) in _data, e.g.,

// _data/dataUrls.json
[
{
"name": "abc",
"url": "http://abc.com"
},
{
"name": "xyz",
"url": "http://xyz.com"
}
]
<!-- in a .njk file -->
{% for item in dataUrls %}
{{ item.name }}
{{ item.url }}
{% endfor %}
// _data/cat_icon.json
{
"Project-based Learning": {
"svg": "/img/cats/project.svg"
},
"MOOC": {
"svg": "/img/cats/mooc.svg"
}
}
// .eleventy.js
const catIcon = require("./_data/cat_icon.json")
// usage
catIcon[page.data.tags[1]].svg,

// .njk
catIcon[page.data.tags[1]].svg,

For example, export a current year on site,

// _data/helpers.js
module.exports = {
currentYear() {
const today = new Date();
return today.getFullYear();
}
}
<!-- in a .njk file -->
<div>{{ helpers.currentYear() }}</div>

Fetched JSON from an external source #

For example, this post displays a list of starred github repositories (by me) which is fetched from https://api.github.com/users/dinhanhthi/starred.

// in .eleventy.js
module.exports = function (eleventyConfig) {
eleventyConfig.addShortcode("list_repos", getLiList);
async function getRepoData(_url) {
const response = await fetch(_url);
return await response.json();
}
async function getLiList(){
function compare( a, b ) {
if ( a.name.toLowerCase() < b.name.toLowerCase() ){
return -1;
}
if ( a.name.toLowerCase() > b.name.toLowerCase() ){
return 1;
}
return 0;
}
// escape HTML tags
function htmlEntities(str) {
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
var repos = "";
const data = await getRepoData("https://api.github.com/users/dinhanhthi/starred?page=1&per_page=10000");
data.sort(compare).forEach(obj => {
repos += '<li>'
+ '<a href="' + obj.html_url + '" target="_blank">'+ obj.name +'</a>'
+ ' by <i>' + obj.owner.login + '</i> β€” ' + htmlEntities(obj.description)
+ '</li>';
});
return '<ol>' + repos + '</ol>';
}
}
// in post
{% list_repos %}

Working style #

Custom environment #

More info, read official doc. For example, we only perform something differently on local.

{
"scripts": {
"local-build": "ELEVENTY_ENV=local eleventy"
}
}

An example of using in .eleventy.js,

// .eleventy.js
module.exports = {
environment: process.env.ELEVENTY_ENV
};

module.exports = function (eleventyConfig) {
if (process.env.ELEVENTY_ENV == "local"){
// do something locally
} else {
// do something on server
}
}

Or using in the template,

// _data/myProject.js
module.exports = {
environment: process.env.ELEVENTY_ENV
};
{% if myProject.environment == "local" %}
<style>{{ css | cssmin | safe }}</style>
{% else %}
<style>{{ css | safe }}</style>
{% endif %}

Incremental build #

It's impossible for the current version (^0.11.0)! (Follow the main project).

Weakness of 11ty:

  1. There is some change in files, 11ty rebuilds the whole site. It's painful if we work with markdown files and save them regularly!
  2. Cannot access the site while the building processing.

Idea:

  1. Build manually, e.g. npm run build-local to _site folder.
  2. Copy all files in _site to a so-called folder _live
  3. Run a custom server on folder _site (install npm i http-server -g first)

An example of scripts,

{
"scripts": {
"local-build": "ELEVENTY_ENV=local eleventy && mkdir -p _live && cp -Rf _site/* _live/",
"local-serve": "mkdir -p _live && cp -Rf _site/* _live/ && http-server _live",
}
}

Nunjucks things #

Add a new item to a list,

{% set a = [1,2] %}
{% set a = (a.push(3),a) %}

Create a dictionary with nunjucks

{% set items = {'a': 1, 'b': 2} %}

Add a new key-value to a dictionary,

// .eleventy.js -> create a new filter
eleventyConfig.addFilter('setAttribute', function(dictionary, key, value) {
dictionary[key] = value;
return dictionary;
});

// usage
{% set myDict = {"key1": 0, "key2": 0, "key3": 0}%}
{% set myDict = myDict|setAttribute('key2', 123) %}
{{myDict['key2']}} // pring "123" as expected

Errors #

# TypeError: Cannot read property 'type' of undefined
# => Class comes before ![]() of an image!
# EISDIR: illegal operation on a directory
# Solution:
# Delete _site/ and rebuild!
# ENOTDIR: not a directory...
# Solution:
# Delete _site/ and rebuild!
# Invalid DateTime
# Just for new posts => try to commit (on branch "dev") before building!