Creating a Website with 11ty

I've been running my personal website for years, but I've never actually documented how I built it. And since I haven't written an article in ages I started by redoing my website from scratch using 11ty. 🙃 In this article, I'll walk through the process of creating my website using 11ty (Eleventy), including the tools and workflows I use to manage content.

Why 11ty?

I chose 11ty for several reasons:

Project Structure

My site follows the following structure:

src/
├── _includes/         # Reusable templates
├── css/               # Stylesheets
├── img/               # Images
└── posts/             # Content
    ├── articles/
    ├── snippets/
    └── short-stories/

I also pepper in images within subfolders and collect them with these lines added to .eleventy.js

  eleventyConfig.addPassthroughCopy("src/css");
eleventyConfig.addPassthroughCopy({ "src/**/*.pv.*": "img/" });
eleventyConfig.addPassthroughCopy({ "src/**/*.main.*": "img/" });
eleventyConfig.addPassthroughCopy({ "src/**/*.zip": "download/" });

For syntax highlighting I use @11ty/eleventy-plugin-syntaxhighlight.

Image Processing Workflow

I use ImageMagick to process images before publishing. This ensures optimized, web-ready images, with meta data removed.

mk_image.sh

This script resizes images to web-friendly dimensions:

#!/bin/bash
# mk_image.sh - Resize image to 1024x1024 cropped and create 128x128 preview
# Usage: ./mk_image.sh input_image.jpg

set -e

if [ $# -lt 1 ]; then
echo "Usage: $0 input_image.jpg"
exit 1
fi

INPUT_FILE="$1"

# Check if input file exists
if [ ! -f "$INPUT_FILE" ]; then
echo "Error: File '$INPUT_FILE' not found"
exit 1
fi

# Get directory and filename
INPUT_DIR=$(dirname "$INPUT_FILE")
FILENAME=$(basename "$INPUT_FILE")
NAME="${FILENAME%.*}"
EXT="${FILENAME##*.}"

# Convert HEIC to JPG
if [[ "$EXT" == "heic" ]] || [[ "$EXT" == "HEIC" ]]; then
OUTPUT_NAME="${NAME}.jpg"
# Convert HEIC to JPG using sips
sips -s format jpeg "$INPUT_FILE" --out "${INPUT_DIR}/${OUTPUT_NAME}" > /dev/null
INPUT_FILE="${INPUT_DIR}/${OUTPUT_NAME}"
FILENAME="${OUTPUT_NAME}"
NAME="${OUTPUT_NAME%.*}"
EXT="jpg"
fi

# Output paths (same folder as source)
PREVIEW_FILE="${INPUT_DIR}/${NAME}.pv.jpg"
RESIZED_FILE="${INPUT_DIR}/${NAME}.main.jpg"

echo "Processing '$INPUT_FILE'.."

# Check if output files already exist
if [ -f "$RESIZED_FILE" ] && [ -f "$PREVIEW_FILE" ]; then
echo "Output files already exist, skipping"
echo " $RESIZED_FILE"
echo " $PREVIEW_FILE"
exit 0
fi

# Resize to 1024x1024 cropped from center with web optimization
convert "$INPUT_FILE" \
-resize '1024x1024^' \
-gravity center \
-crop '1024x1024+0+0' \
+repage \
-quality 85 \
-strip \
-interlace Plane \
"$RESIZED_FILE"
echo "Created: $RESIZED_FILE"

# Create 128x128 cropped preview from center with web optimization
convert "$INPUT_FILE" \
-resize '128x128^' \
-gravity center \
-crop '128x128+0+0' \
+repage \
-quality 85 \
-strip \
"$PREVIEW_FILE"
echo "Created: $PREVIEW_FILE"

echo "Done!"

mk_all_images.sh

Process all images in a directory at once:

#!/bin/bash
# mk_all_images.sh - Process all images in a folder using mk_image.sh
# Usage: ./mk_all_images.sh input_folder

set -e

if [ $# -lt 1 ]; then
echo "Usage: $0 input_folder"
exit 1
fi

INPUT_FOLDER="$1"

# Check if input folder exists
if [ ! -d "$INPUT_FOLDER" ]; then
echo "Error: Folder '$INPUT_FOLDER' not found"
exit 1
fi

# Get script directory for mk_image.sh path
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

echo "
Processing all images in '$INPUT_FOLDER'..."

# Find and process all images, excluding .pv. and .main. variants
for file in "
$INPUT_FOLDER"/*; do
# Skip if not a file
[ -f "
$file" ] || continue

filename=$(basename "$file")

# Skip preview and resized images
if [[ "
$filename" == *.pv.* ]] || [[ "$filename" == *.main.* ]]; then
continue
fi

# Skip non-image files (basic check)
if ! [[ "
$filename" =~ \.(jpg|jpeg|png|gif|webp|bmp|heic|HEIC)$ ]]; then
continue
fi

echo "
Processing: $filename"
"
$SCRIPT_DIR/mk_image.sh" "$file"
done

echo "
Done!"

Image Macro in Nunjucks

I use a Nunjucks macro to render images across my site:

{% macro image(src, alt, caption=None) %}
<figure class="image-wrapper">
<img src="/img/{{ src }}" alt="{{ alt }}" class="image" />
{% if caption %}
<figcaption>{{ caption }}</figcaption>
{% endif %}
</figure>
{% endmacro %}

And call it with

{% from "image.njk" import image %}

{{ image("image.main.jpg", "ALT text", "Optional Caption") }}

This macro:

The style I use is:


.image-wrapper {
text-align: center;
margin: 2rem 0;
}

.image-wrapper .image {
max-width: 66.666%;
width: 100%;
height: auto;
display: block;
margin: 0 auto;
border-radius: 4px;
}

.image-wrapper figcaption {
margin-top: 0.5rem;
font-size: 0.875rem;
}

Fyi: if you wnat to render nunjucks statements in code blocks, use this syntax around the block:

{% raw %}
...
{% endraw %}

Writing Content

Posts are written in Markdown with YAML frontmatter:

---
title: "My Post Title"
date: 2026-03-01
tags: ["tag1", "tag2"]
layout: article.njk

---


Content goes here...

Building

To build the site, run:

npm run build

The output in the dist/ folder is ready to be deployed to any static hosting service.

Discover content by tag:

© 2026 Paul Smith Contact & Privacy Policy