How this website works
This page explains how this website was created. Hopefully, it may be a source of inspiration for creating your own website! I make use of Eleventy (11ty)βa simple and fast static site generatorβfor making this website. You might want to check out the Eleventy Getting Started page to start with your own website. Afterwards, you can check out this document for some inspiration on how to personalize it further.
β° Overview
The basic premise of Eleventy (and static site generators more
generally) is thatβwith some configurationβyou can generate a website
from a collection of text files. These text files are often in
markdown, but I personally use Org-mode instead (hence the
README.org
instead of README.md
) :) Using a
static site generator makes writing new posts much easier, because you
don't have to do that in HTML directly! Moreover, you can even create
web pages from a JSON file in Eleventy (I do so for the art page, which
is generated from a file called art.json
containng a list
of images I want to include).
When you initialize your project following the Getting Started I referred to above, you will see a file structure in your project directory similar to this one (though I have adapted this structure to my liking):
βββ _includes
// Contains the layout/template files
βββ _11ty
// Contains some extra functions for my website (e.g., for loading images for my art page)
β βββ shortcodes.js
βββ _data
// Contains json files, one for describing some metadata and another for generating my art page
β βββ art.json
β βββ metadata.json
βββ _site
β // Here you find the exported website you can upload to the web
βββ assets
// Here you can store your assets, like your images and other static files you may want to have on your website
βββ posts
// Contains my blog posts as text files
βββ pages
// Here I put my web pages that are not blog posts
βββ README.org
βββ index.org
βββ marx.min.css
βββ extra.css
βββ .eleventy.js
βββ .eleventyignore
In the following sections, I'll explain:
- the main configuration file (
.eleventy.js
), where you define your plugins, filters, and static files; - the structure of a post and how to define its layout (and as a result, how your website is visually structured); and
- the theme of the website using some CSS.
I also provide some tips on some more specific things, like how I added a navigation bar and a gallery to this website. This document ends with a Makefile that also serves as a cheat sheet.
π½ Configuration: plugins, filters, and moving files
The main configuration file for your Eleventy website is
.eleventy.js
.
π .eleventy.js
Your .eleventy.js
is the main file for configuring your
website. It basically consists of two parts: in the beginning you define
your plugins (like import
in Python or
\usepackage{}
in LaTeX) and later your actual configuration
in function(eleventyConfig) {}
.
π¦ Plugins and packages
Here are some packages I found useful (don't forget to install them first!):
// Add RSS support
const pluginRss = require("@11ty/eleventy-plugin-rss");
// Add syntax highlighting in your codeblocks
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
// Add a navigation plugin (useful for links at the top for navigating your website)
const eleventyNavigationPlugin = require("@11ty/eleventy-navigation");
// For pretty date formatting
const { DateTime } = require("luxon");
β Functions
Now we define the main configuration in the
module.exports = function(eleventyConfig) {}
block.
We start with adding our 11ty plugins:
module.exports = function(eleventyConfig) {
// Add plugins
// Add syntax highlighting
eleventyConfig.addPlugin(syntaxHighlight);
// Add RSS support
eleventyConfig.addPlugin(pluginRss);
// Add navigation plugin
eleventyConfig.addPlugin(eleventyNavigationPlugin);
Here we copy all the static files we want to include on our website:
// Copy files you need for your website
// If you store your images in `assets/`,
// you want to copy this directory to your website
eleventyConfig.addPassthroughCopy("assets");
// Add your css files to your website
eleventyConfig.addPassthroughCopy("marx.min.css");
eleventyConfig.addPassthroughCopy("extra.css");
Lastly, we add some filters that we can use in our templates to transform certain data into nicer textual representations. For example, these two filters create an excerpt for a post's content (maybe in the future I could use a summarization language model?) and prettify the date string:
// Define your filters
// Define a filter for showing summaries of your posts
eleventyConfig.addFilter('excerpt', (post) => {
const content = post.replace(/(<([^>]+)>)/gi, '');
return content.substr(0, content.lastIndexOf(' ', 200)) + '...';
});
// Add pretty dates support
eleventyConfig.addFilter("asPostDate", (dateObj) => {
return DateTime.fromJSDate(dateObj).toLocaleString(DateTime.DATE_MED)
});
}
πΊ Layouts: Individual posts and the emergence of structure
I store most of my posts and pages as text files in
posts/
and pages/
, respectively. Each of these
files is simply in the format you expect (markdown,
orgmode), with the exception of some information at the top of
the file: Here you provide information like the title, date, but also
which layout file should be used, whether there should be a link in your
navigation bar, etc.
For example, this file has the following information at the top:
---
title: "How this website works"
layout: post.njk
eleventyNavigation:
key: init.el
parent: Pinned
order: 4
---
Layout
In _includes/
, you have your templates that define how
your markdown/orgmode files should be translated into actual HTML files.
Eleventy supports multiple "templating languages", such as HTML
(.html
) or nunjucks
(.njk
). I use nunjucks. At the top of your posts you define
which layout file must be used, of which I currently a
base.njk
shared by all pages, and one for my home page
(home.njk
) and all other pages (post.njk
).
π base.njk
This template is the basis for all of my web pages. Here I define the
title of the page, the CSS styling, the navigation bar, the footer, etc.
Typically, a HTML file consists of a head
and a
body
.
Head
<!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"> <title>OvdW - {{ title }}</title> <link href="https://unpkg.com/[email protected]/themes/prism-okaidia.css" rel="stylesheet"> <!-- Tracking users on the web --> <script async src="https://discreet-raccoon.pikapod.net/script.js" data-website-id="0671c728-4dac-4247-a62c-8e5750662841"></script> <!-- Marx CSS --> <link rel="stylesheet" href="/marx.min.css"> <link rel="stylesheet" href="/extra.css"> <style> :root { --primary: #008512; --link-color: #1a9f60; } </style> <!-- Check if tags exists and whether it contains 'vega' or 'post' to load relevant scripts/css --> {% if tags %} {% if tags.includes('post') %} <link href="https://unpkg.com/[email protected]/themes/prism-okaidia.css" rel="stylesheet"> {% endif %} {% if tags.includes('vega') %} <script src="https://cdn.jsdelivr.net/npm//vega@5"></script> <script src="https://cdn.jsdelivr.net/npm//vega-lite@5"></script> <script src="https://cdn.jsdelivr.net/npm//vega-embed@6"></script> {% endif %} {% endif %} </head>
Body
<body> <!-- <div style="clear: both" class="background sticky"> --> <!-- <h1 class="logo" style="float: left"><a href="/">OvdW</a></h1> --> <div class="wrapper"> <h1 class="logo">OvdW</h1> <p class="top-quote"><i>βLive, Laugh, Loveβ</i></p> <div class="top-right"></div> </div> <br> <main> <div class="background"> <hr /> <nav> {{ collections.all | eleventyNavigation("Pinned") | eleventyNavigationToHtml | safe }} </nav> <!-- <form style="float: right"> --> <!-- <input type="BUTTON" value="Back to top of page" onclick="window.location.href='#top'"> --> <!-- </form> --> <hr /> </div> <!-- <center class=hero> <h1>{{ title }}</h1></center> --> <div class="sticky" style="position: fixed; top: 0; right: 0; border: 0; padding: 10px;"><a href="#top">^top</a></div> <article> <h1 class="title" style="text-align: center; float: center">{{ title }}</h1> {{ content | safe }} </article> <footer> <p>© 2023 · <a href="https://github.com/oskarvanderwal/">Oskar van der Wal</a></p></footer> </main> <script src="https://unpkg.com/[email protected]/components/prism-core.min.js"></script> <script src="https://unpkg.com/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script> </body> </html>
π home.njk
---
"layout": "base.njk"
"title": "Home"
---
{{ content | safe }}
{% for post in collections.post | reverse %}
{% if loop.index0 < 2 %}
<h2><a href="{{ post.url }}">{{ post.data.title }}</a></h2>
<p><b>[{{ post.date | asPostDate }}]</b>—{{ post.templateContent | excerpt }}</p>
<a href="{{ post.url }}">read more</a>
{% endif %}
{% endfor %}
π post.njk
---
"layout": "base.njk"
---
{{ content | safe }}
β¨ Themes: CSS files for creating a visual leitmotif
For styling my website, I use Marx CSS, the classless CSS reset. But it is now that you can be really creative, and you should find a theme that suits your website!
My process is very simple: I simply download a CSS file, marx.min.css
,
and place it in the root folder. Then I add an extra CSS file,
extra.css
, for adding some additional styling where
needed.
π extra.css
@media screen and (min-width: 800px) {
* {
/* Here we set the margin and padding 0 */
margin: 100;
padding: 100;
}
body {
padding-left: 68px;
padding-right: 68px;
}
}
article {
width: 100%
}
nav {
/*it specifies the mouse cursor to be displayed
when it is pointed over the element */
cursor: pointer;
}
nav ul {
flex-flow: row wrap;
display: flex;
justify-content: space-evenly;
}
.background {
background: rgba(255, 255, 255, 0.95);
}
.sticky {
/* This is used to make the navbar sticky, So that the
navbar stays at the top part even if we scroll */
position: sticky;
/* align-items: center; */
/* justify-content: center; */
/* top: 0px; */
}
.wrapper{
display: flex;
justify-content: center;
align-items: baseline;
flex-flow: row wrap;
/* gap: 10px; */
}
.logo {
flex: 1;
font-family: Palatino;
color: black;
/* font-size: rem; */
/* color: white; */
/* background-color: green; */
}
.top-quote {
flex: 1;
font-family: Palatino;
font-size: 24px;
text-align: center;
/* width: 100%; */
/* max-width: 50%; */
/* min-width: 50%; */
}
.top-right {
flex: 1;
}
.back-to-top {
position: absolute;
bottom: 0;
text-align: right;
height: 100%;
position: sticky;
}
.back-to-top a {
position: sticky;
top: 88vh;
cursor: pointer;
font-size: 20px;
}
.back-to-top:before {
content: '';
}
.title {
text-align:center;
}
img {
max-width: 100%;
height: auto;}
.gallery {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.gallery img {
display: block;
object-fit: cover;
}
.gallery li{
display: flex;
max-width: 50%;
}
.gallery figcaption{
text-align: center;
}
π¦ Org-mode: Adding support for my life support
The following packages add org-mode file support to my 11ty setup, but if you only use markdown, you can leave this out of course!
At the start of .eleventy.js
:
// For adding orgmode file support
const nodePandoc_ = require('node-pandoc');
const util = require('util');
const nodePandoc = util.promisify(nodePandoc_)
Within the module.exports = function(eleventyConfig) {}
block of .eleventy.js
:
// Optional: Add support for orgmode files
eleventyConfig.addTemplateFormats('org');
eleventyConfig.addExtension('org', {
compile: async (inputContent, inputPath) => {
const org2html = await nodePandoc(inputContent, "-f org -t html --shift-heading-level-by=1 --no-highlight")
// Replace any instances of "sourceCode " with "language-"
//let output = org2html.replace(/sourceCode /gi, "language-");
// let output = org2html.replace(/(.*<pre class=")(\w*)(" \S*\n\S*)(<code>)/, "$1$2$3<code class=$2>")
let output = org2html.replaceAll(/(<pre.*?class=")(\w*)(".*?)(<code>)/gs, "$1language-$2$3<code class=\"language-$2\">");
return async () => {
return output
}
},
});
πΈ Navbar: Navigation in the age of internet explorers
Using the eleventyNavigationPlugin
, you can add a page
to your navigation bar. Make sure to install
@11ty/eleventy-navigation
first!
Then add eleventyNavigation:
to the top of your pages
that you want in the navbar:
---
title: "How this website works"
layout: post.njk
eleventyNavigation:
key: init.el
parent: Pinned
order: 4
---
Here, I have defined the current post in the category of
Pinned posts, order 4, and key init.el
(shown in
the navbar). Check the base.njk
template file to see how I
use this to add pinned pages to the navbar.
π¨ Galleries: L'Art pour l'art(ificial intelligence)
Adding images to your website is easy. However, doing it nicelyβsuch as resizing it depending on the screen sizeβis more difficult. While 11ty has its own image plugin, I have instead chosen to use a file hosting & processing service called cloudinary.
π _11ty/shortcodes.js
const CLOUDNAME = "dgpbva9o1";
const FOLDER = "v1682085335/Art/";
const BASE_URL = `https://res.cloudinary.com/${CLOUDNAME}/image/upload/`;
const FALLBACK_WIDTHS = [300, 600, 680, 1360];
const FALLBACK_WIDTH = 680;
function getSrcset(file, widths) {
const widthSet = widths ? widths : FALLBACK_WIDTHS;
return widthSet
.map((width) => {
return `${getSrc(file, width)} ${width}w`;
})
.join(", ");
}
function getSrc(file, width) {
return `${BASE_URL}q_auto,f_auto,w_${
width ? width : FALLBACK_WIDTH
}/${FOLDER}${file}`;
}
function getThumbnail(thumbnail, alt) {
return `<img src="${BASE_URL}c_thumb,w_200,g_face/${FOLDER}${thumbnail}" alt="Link to ${alt}">`;
}
function getImage(file, alt, width) {
src = getSrc(file, width);
return `<img src="${src}" alt="${alt}">`;
}
// thumbnail is already taken, so defining small_image instead
module.exports = {
srcset: (file, widths) => getSrcset(file, widths),
src: (file, width) => getSrc(file, width),
defaultSizes: () => "(min-width: 980px) 928px, calc(95.15vw + 15px)",
small_image: (image, alt) => getThumbnail(image, alt),
image: (file, alt) => getImage(file, alt, 680)
};
Add to the top of .eleventy.js
:
// Shortcodes
const shortcodes = require("./_11ty/shortcodes.js");
Add to the top of the .eleventy.js
function block:
// Add the functions defined in shortcodes
Object.keys(shortcodes).forEach((shortcodeName) => {
eleventyConfig.addShortcode(shortcodeName, shortcodes[shortcodeName]);
});
π¬ Analytics: Measuring simple user statistics
Of course it is nice to know who visits your website! While back in the day, having a guestbook (and lots of animated emoticons) on your website was cool, now we simple use a tracking tool to see where people are from. Be aware though that your user-tracking should be GDPR-compliant!
I use the simple self-hosted tool Umami, which provides you with a
simple javascript to hide on your website. See if you can find the
discreet raccoon in my base.njk
?
π±βπ» Makefile: your sysadmin and cheat-sheet
π Makefile
help:
@echo 'Makefile for my Website '
@echo ' '
@echo 'Usage: '
@echo ' make install install 11ty and all plugins '
@echo ' make clean remove the generated files in _site '
@echo ' make publish generate and sync using production settings '
@echo ' make serve serve site at http://localhost:8080 '
@echo ' make github upload the web site to git submodule of _site '
@echo ' '
install-11ty:
npm init -y
npm install @11ty/eleventy --save-dev
install-plugins:
npm install @11ty/eleventy-img --save-dev
npm install @11ty/eleventy-plugin-syntaxhighlight --save-dev
npm install @11ty/eleventy-navigation --save-dev
install:
install-11ty
install-plugins
clean:
[ ! -d '_site' ] || rm -rf '_site'
serve:
npx @11ty/eleventy --serve
publish:
npx @11ty/eleventy
bash run_sync.sh
github:
cd '_site' && git add . && git commit -m "Update website" && git push
Some notes:
make install
: Not really necessary in a Makefile, but for the sake of completeness.make github
: If you make_site/
a git (sub)module, you can use this to easily sync with github pages.make publish
: I use rsync to copy_site/
to my file server.
Running website for development
The following will host your website at localhost:8080
and reloads automatically when you make any changes:
npx @11ty/eleventy --serve
Installing a plugin
Run the following in your project root to add a plugin, e.g., for
eleventy-img
:
npm install @11ty/eleventy-img --save-dev