Home / Articles / Static Site Generators From Zero: Vocabulary, History and a First Build

This article is published in English.

Static Site Generators From Zero: Vocabulary, History and a First Build

Learn the terms SSG docs assume you know, how layouts, partials and front matter fit together, where generators came from, and how to pick one without the friction.

5211 words

Static site generators promise a simple deal: write posts in Markdown, keep the header and footer in one place, and get a fast website made of plain files. For many newcomers the reality is a wall of unexplained jargon, a terminal that prints stack traces, and documentation that assumes years of background knowledge. This guide closes that gap from the ground up. You will learn which skills to have before you start, what every recurring term in generator documentation actually means, how the pieces of a typical Eleventy project fit together, where these tools came from, and which lighter-weight options exist when the popular ones feel like too much.

Before you pick a generator: the skills underneath

A static site generator (SSG) is an abstraction layer on top of web pages. If you have never built a web page by hand, that abstraction hides exactly the things you need to understand when something goes wrong. For your first few sites, writing the HTML and CSS yourself is the better teacher. You will feel the pain of copying the same navigation into ten files, and that pain is precisely what makes a generator click later.

Almost every generator quietly expects you to be comfortable with HTML and CSS, often a little JavaScript, some general programming ideas such as variables and loops, the command line, and usually Git. Nobody needs to master all of these first. The point is that a basic mental model of each layer turns a cryptic build failure into a solvable problem instead of a mystery.

A study path of free resources

The following resources work well roughly in this order. Build small throwaway sites as you go rather than treating the list as homework to finish first.

  • HTML: HTML for People is written for readers with zero coding experience. When you want more depth on semantic elements and accessibility, move to MDN's module on structuring content.
  • CSS: MDN's styling basics module covers concepts like the box model and layout. If hands-on exercises suit you better, the freeCodeCamp course on responsive design walks through HTML, CSS, accessibility and responsive (in practice, mobile-friendly) design.
  • JavaScript: MDN's scripting module continues naturally from its HTML and CSS material, and the freeCodeCamp JavaScript curriculum is an interactive alternative.
  • Programming fundamentals: CS50x, Harvard's free computer science introduction, teaches computational thinking, algorithms, data structures, functions, conditionals and loops. The first few weeks alone give a solid base.
  • The terminal: MIT's Missing Semester course covers working in the shell, editors, the command-line environment and debugging techniques. Its shell lecture is the place to begin.
  • Git: the free online book Pro Git begins with the command line and simple commits, then moves on to branches, remotes and hosting.
  • Markdown and YAML: for Markdown, a basic syntax reference shows which symbols produce which formatting. The YAML 1.2 specification is far more detailed than a blogger needs, but its introduction is a clear overview.
  • Validation: the W3C Nu HTML Checker flags invalid markup so you can catch mistakes early.

If you prefer one hub instead of a collection, MDN's learning area covers HTML, CSS, JavaScript and browser fundamentals in a single curriculum. web.dev, The Odin Project and w3schools are further options.

Keep perspective: a personal website is usually a hobby project. Unconventional solutions and mistakes are part of the process, and each small site you finish adds a new capability you can fold into the next one.

The vocabulary static-site docs assume you know

Generator documentation tends to use a dozen terms as though everyone learned them at birth. This section defines them in plain language and shows each one in a small, concrete file from an Eleventy (11ty) project.

Markup, style and behaviour: HTML, CSS and JavaScript

HTML (HyperText Markup Language) describes the structure and meaning of a document: headings, paragraphs, links, images, lists. It is not a programming language. It cannot make decisions or repeat anything on its own; it simply declares what is on the page. The smallest useful page has a doctype, a <head> with a character set and title, and a <body> with content. Notice that nothing here controls colours or fonts, so the browser falls back to its default styles.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>My First Page</title>
  </head>
  <body>
    <h1>Hello, world!</h1>
    <p>This page has a <a href="https://brennan.day">link</a> and a list:</p>
    <ul>
      <li>HTML gives a page its structure.</li>
      <li>There's no CSS yet, so this is all default styling.</li>
    </ul>
  </body>
</html>Copy

Saving this as index.html and opening it in any browser gives you a working web page, no server required. (The trailing Copy after the closing tag is a leftover from a copy button and is not part of the markup.)

CSS (Cascading Style Sheets) controls how that structure is presented: colours, spacing, typography, and how the layout adapts to different screen sizes. The rules below set a serif font, cap the text width so lines stay readable, centre the column with automatic margins, and give the page warm background and dark text colours. The heading gets its own colour rule.

body {
  font-family: Georgia, serif;
  max-width: 35rem;
  margin: 2rem auto;
  padding: 0 1rem;
  background: #fff2ce;
  color: #02005d;
}

h1 {
  color: rebeccapurple;
}Copy

Placing these rules inside a <style> element in the <head> of the earlier page transforms its appearance without changing a single word of the HTML. That separation of content from presentation is a theme that carries all the way through static site generators.

JavaScript is the programming language browsers run. It adds behaviour: reacting to clicks, changing content, fetching data. It is also the easiest way to make a simple page heavy and slow, so a good default for a personal site is to use it only where it clearly earns its place. The snippet below finds an element with the id surprise, listens for clicks on it, and replaces the text of the first <h1> when the click happens.

const button = document.querySelector("#surprise");

button.addEventListener("click", () => {
  document.querySelector("h1").textContent = "JavaScript did this!";
});Copy

For this to work, the page needs a matching <button id="surprise">. Without it, querySelector returns null and the call to addEventListener throws an error, which is a common first bug.

JavaScript also shows up on the generator side, not only in the browser. Many SSGs are themselves written in JavaScript and use it to run the build or evaluate templates. Eleventy even lets a whole template be a JavaScript file: whatever string the exported function returns becomes the page content.

// hello.11ty.js
module.exports = function () {
  return "<h1>Hello from JavaScript!</h1>";
};Copy

This example uses CommonJS module.exports. Recent Eleventy versions also support ES module syntax (export default), so check which style the documentation for your installed version uses.

What "static", "build" and "output" actually mean

  • Static describes delivery: the server hands over a file exactly as it is stored, instead of assembling a fresh response for each visitor. A static page can still include JavaScript and can still be edited and redeployed. It says nothing about the page being dull or frozen forever.
  • Dynamic means something computes the response at request time. A classic content management system queries a database and stitches a page together on every visit. An online shop is the textbook case, because inventory and carts change constantly.
  • A static site generator is a program that reads source material (Markdown content, templates, configuration, images and other assets) and produces a finished set of HTML, CSS, JavaScript and image files that any static host can serve.
  • A build is one run of the generator's command that turns sources into output.
  • Source files are what you edit: posts, templates, stylesheets, configuration.
  • Output files (or built files) are what the build produces, typically in a folder such as _site/, public/ or dist/. You generally do not edit these by hand, because the next build overwrites them.
  • A local server is a web server running on your own machine. A development server serves the built site at an address like localhost:8000 and often rebuilds automatically whenever you save a source file.

Configuration files and site data

  • A configuration file holds site-wide settings such as the site name, base URL, menus, output directory or feed options. Names and formats differ by tool: config.yml, hugo.toml, eleventy.config.js and others.
  • YAML is a human-friendly data format common in configuration and front matter. It expresses strings, numbers, lists and key-value mappings. Indentation carries meaning, so one misplaced space can break a build.
  • A key-value pair is a setting with a name and a value, such as title: My post. In YAML, a group of these pairs is called a mapping.
  • A parameter or option is a setting you pass to a command or put in a file. The --serve flag you will meet later is one example.

In Eleventy, files in the _data folder become global data available to every template. The file src/_data/site.json below stores the details visitors see: the site name, a short description, the author, the public URL and the language.

{
  "name": "My Cool Blog",
  "description": "Where I write about whatever interests me.",
  "author": "Your Name",
  "url": "https://example.com",
  "language": "en"
}

Each key turns into a template variable. A layout that contains {{ site.name }} renders the value "My Cool Blog", so renaming the site means editing one line here instead of hunting through every page. Jekyll keeps the same kind of information in config.yml and Hugo in hugo.toml; the idea is identical, only the file changes. Be aware that JSON is strict: a trailing comma after the last entry is a syntax error, a detail that becomes important later in this guide.

Layouts, partials and templating

  • A template is a reusable file that defines the shape of a page and contains placeholders for the parts that change.
  • A layout is a template for an entire page: the language declaration, the <head>, header, main content area and footer.
  • A partial is a small reusable fragment such as a navigation bar, a footer or a block of post metadata. An include is the instruction that pulls one fragment into another file.
  • A templating language is the syntax for printing variables, looping over data and making decisions inside templates. Liquid, Nunjucks and Go templates are common examples.
  • A conditional is a yes-or-no rule in a template, for example "render the hero image only when the post defines one".

The base layout below, _includes/layouts/base.njk, is written in Nunjucks. The title combines the page's own title with the global site name, two include tags insert the header and footer partials, and the rendered page body is printed inside <main>.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>{{ title }} | {{ site.name }}</title>
  </head>
  <body>
    {% include "partials/header.njk" %}
    <main>
      {{ content | safe }}
    </main>
    {% include "partials/footer.njk" %}
  </body>
</html>

The | safe filter matters. Nunjucks escapes output by default, which would turn the post's HTML into visible tags. Marking content as safe tells the engine that this string is trusted, already-rendered HTML. Only use it for content you control.

The partials themselves are just HTML fragments that may use variables and include further partials. The header links back to the home page using the site name and pulls in the navigation; the navigation is a plain list of links; the footer prints a copyright line with the author from the site data.

<!-- partials/header.njk -->
<header>
  <a href="/">{{ site.name }}</a>
  {% include "partials/nav.njk" %}
</header>

<!-- partials/nav.njk -->
<nav>
  <a href="/">Home</a>
  <a href="/archive/">Archive</a>
  <a href="/about/">About</a>
</nav>

<!-- partials/footer.njk -->
<footer>
  <p>&copy; 2026 {{ site.author }}</p>
</footer>

Here is how the pieces connect. When a post declares layout: base.njk in its front matter, Eleventy renders the post, then places the result where {{ content | safe }} sits in the layout, and each include tag is replaced by its partial. Change the navigation once and every page on the site picks it up on the next build. Eliminating that copy-and-paste maintenance is the core reason SSGs exist. One small caveat: the year in the footer is hard-coded, so it will not update itself unless you replace it with a variable.

Content files, Markdown and front matter

  • A content file is a source file for a page or post. Markdown is the most common format, but many generators accept HTML, plain text or others.
  • Markdown is a lightweight markup language in which punctuation stands for structure: # for headings, asterisks for emphasis, dashes for lists. The generator converts it to HTML.
  • Front matter is a metadata block at the very top of a content file, usually fenced by two lines of three dashes. It can hold a title, date, tags, layout name or draft flag.
  • Metadata is descriptive information about a piece of content: title, author, publication date, tags, description, canonical URL or chosen layout.
  • A blog-aware generator understands posts as a concept. It can order them by date and produce archive pages, tag pages and an RSS feed without extra work.

The file posts/my-first-post.md below combines all of these. The YAML front matter sets a title, date, two tags, a layout and a draft flag. The body mixes ordinary Markdown with Nunjucks-style template syntax that prints the title and conditionally shows a sentence.

---
title: My First Post
date: 2026-09-22
tags:
  - posts
  - cats
layout: post.njk
draft: false
---
Welcome to my blog! This paragraph is **Markdown**.

This post is called "{{ title }}".

{% if draft %}
  This sentence only appears while the post is a draft.
{% endif %}

Eleventy reads the front matter before rendering anything. The layout key picks the wrapping template, the posts tag adds the file to a collection called posts (which an archive page can loop over), and date gives the blog-aware sorting its order. Everything after the closing dashes is the body. Because draft is false here, the conditional sentence does not appear in the output. Note that a draft key has no built-in meaning in Eleventy; excluding drafts from a production build is something you configure yourself.

Hosting, backends and deployment terms

  • Hosting is the service or machine that stores your output files and makes them reachable on the internet. It is a separate concern from writing the site and from version control.
  • A backend is server-side code and infrastructure: authentication, form handling, business rules, database queries. A purely static site needs none of it to serve pages.
  • A database is structured storage that software can query and update, a little like a programmable spreadsheet. A traditional dynamic blog keeps posts, comments and settings there. When you see SQL mentioned, a database is involved.
  • FTP (File Transfer Protocol) moves files between computers, typically from your machine to a web host, and is one way to publish.
  • rsync is a command-line tool that synchronises folders and transfers only what changed, which makes it well suited to uploading a rebuilt site.
  • Link validation checks for links that point to pages that do not exist. Some generators do it during the build; others rely on a plugin or an external tool.

Version control in one page

  • Version control records changes to files over time so you can review history, compare versions, roll back and collaborate.
  • Git is one particular version control program. It runs entirely on your computer and needs no online service to track history.
  • A repository (repo) is a project folder whose history Git manages.
  • A commit is a saved snapshot of changes, usually with a message describing them.
  • A remote is another copy of the repository, often hosted on Codeberg, GitHub, GitLab or your own server.
  • Push sends your local commits to a remote; pull fetches commits from a remote and merges them into your local copy.
  • Git hosting services store repositories and often add issue tracking, code review and automated builds. They are convenient, but they are not Git itself, and Git works fine without them.

Publishing a new post with Git from the terminal takes four commands. The first runs only once per project; the other three are the everyday loop of staging a file, recording a snapshot and sending it to the remote.

git init                             # turn this folder into a repository (once)
git add posts/new-post.md            # stage the file for your next commit
git commit -m "Add new post"         # save a snapshot with a message
git push                             # copy your commits to the remoteCopy

In practice git push only works after a remote has been configured, for example with git remote add origin <url>, and the first push of a branch usually needs git push -u origin main or similar. Once that is set up, a plain git push is enough.

Where static site generators came from

With the vocabulary in place, the history makes more sense, because each generation of tools added one of the concepts above.

Separating writing from markup long predates the phrase "static site generator". HSC, short for "HTML Sucks Completely", was an HTML preprocessor released by Thomas Aglassinger in 1996. It already offered includes, conditionals and link validation, about a decade before the category had a name.

Through the late 1990s and the 2000s, most people who wanted a blog chose hosted, dynamic services such as Blogger, LiveJournal or Open Diary, or installed database-backed software like WordPress. Movable Type, a Perl platform created by Ben and Mena Trott in 2001, took a different route: each time you published through its web interface, it regenerated the blog as plain static HTML files. Users never touched a terminal, yet readers were served static pages. It brought the advantages of static output to people who would never type a build command.

Nanoc arrived in 2007, written by Denis Defreyne after Ruby content management systems proved far too slow on his 96 MB virtual server. It introduced layouts, per-page metadata, Markdown support and plugins. In December 2008, GitHub co-founder Tom Preston-Werner released Jekyll, motivated by dissatisfaction with heavyweight blogging engines. Jekyll built on Nanoc's ideas and contributed two defining features: YAML front matter at the top of each content file, and blog awareness out of the box, so a folder of Markdown files became a blog with no extra setup. GitHub Pages launched alongside it as free static hosting, and that pairing did more than anything else to make SSGs mainstream.

Nearly everything since has been a reinterpretation of the same pattern in other languages. Octopress, now discontinued, and Middleman continued the Ruby line. Pelican is Python-based, and Hyde builds on Laravel.

Steve Francia followed in July 2013 with Hugo, a Go program distributed as a single compiled binary. Compared with Jekyll, there was no Ruby environment to install and no gem versions to reconcile, and its build speed, measured in seconds even for sites with thousands of pages, became its signature.

Late in 2017, Zach Leatherman released Eleventy (11ty), a flexible alternative to Jekyll that runs on JavaScript and installs through npm. Jekyll ties you to Liquid; Eleventy accepts a long list of template formats:

  • markup and content: plain HTML (.html), Markdown (.md) and MDX (.mdx)
  • JavaScript-flavoured templates: .11ty.js files, TypeScript (.ts), JSX (.jsx) and WebC (.webc)
  • classic template languages: Liquid, Nunjucks (.njk), Handlebars (.hbs), Mustache, EJS, Haml and Pug
  • stylesheets written in Sass (.scss)
  • any custom extension you register yourself

Some of these formats require a plugin or extra configuration rather than working out of the box, so check the current Eleventy docs before relying on one. If you already know a particular programming language, Jamstack.org's directory of generators lets you filter for a tool written in it.

Many generators in that directory have not seen a release in years, and for a personal site that is often acceptable. A static site has no server-side code or database exposed to visitors, which removes the most common attack surface of dynamic blogs. If a new version of your generator adds features you dislike, you can keep using the old one, and it will go on producing the same site. The caveat is that "no updates" is not the same as "no risk": the dependencies used at build time, the machine that runs the build and any third-party JavaScript you embed still deserve attention, and an unmaintained tool may eventually stop installing cleanly on a newer operating system or language runtime.

Git solves two problems, and you need neither

Beginner guides almost always tell you to use Git. Part of that is simply habit among developers, but part is historical: Jekyll, the first widely adopted SSG, started life as a GitHub project. Hosting on GitHub, Codeberg or GitLab answers the question "where do my files live?" with "in the repository". Services like Neocities or Nekoweb answer it differently: you upload files through the site.

On Git-based hosting such as Codeberg Pages or GitLab Pages, even edits made in the web interface become commits and pushes behind the scenes. You are using Git whether or not you ever type a Git command.

If you host the site yourself, the files live on your own machine, typically in a directory like /var/www/html on Linux. On a shared community machine such as a Tildeverse server, they live in your account's public folder; a common workflow there is to build locally and use rsync to copy the result to the shared computer, where it is served automatically.

It helps to separate the two jobs Git is doing in these setups:

  • keeping a version history, so you can undo a bad edit and see what changed and when
  • getting built files to the place they are served from

Neither job strictly requires Git. rsync, an FTP client or a browser upload form all publish a site perfectly well, and ordinary backups can stand in for history on a small personal project. Git is popular because it handles both jobs at once, costs nothing and plugs into free hosting, which makes it the path of least resistance rather than a requirement.

The gap between the tidy diagram and a real build

On paper, the architecture of a blog generator is almost boringly neat:

  • posts are Markdown files in a posts/ folder
  • a template in a layout/ folder renders them
  • that template assembles HTML fragments from partials/, such as header.html and footer.html
  • a config.yml (or equivalent) at the project root holds site-wide parameters like the name or colours
  • each post also carries its own front matter between two lines of dashes
  • that front matter stores the post's title, date and tags, so the generator can sort and label content without encoding everything in a filename like "2024-03-14-my-post-title.md"

The part the diagram leaves out is the machinery. Turning those folders into a rendered site in, say, _site/ requires a programming language runtime to execute the generator, and every link in that chain is a place where things can fail.

Mainstream tools try hard to hide this behind one command, such as hugo build or npx @11ty/eleventy --serve. The --serve option in the Eleventy command starts a local development server and rebuilds whenever you save a source file, rather than building once and exiting. That quick feedback loop is what makes editing a static site feel almost as immediate as editing a live page.

Deployment platforms extend the same idea to the cloud. Netlify, or the self-hostable Coolify, run your build on a remote machine, detect which generator you use, execute the appropriate command and publish the output folder at an address like yoursitename.netlify.app. Conceptually that is the same as uploading hand-written HTML to Neocities and getting yoursitename.neocities.org, except that the build happens on their machine. Comparable workflows are offered by surge.sh, GitHub Pages, Vercel and, from Cloudflare, its Pages product; which one fits depends on how much you value convenience versus independence from large platforms. If you want to see a complete deployment flow end to end, our walkthrough on shipping a small website to Cloudflare covers one concrete path.

Why a single trailing comma can break everything

Every one of those conveniences rests on assumptions: your files are correct, the language runtime installed cleanly, and you are at ease in a terminal. Developers routinely overestimate how common that last skill is, a blind spot this XKCD comic captures well.

The build is also brittle. One small syntax slip in an important file, as minor as an extra comma, can stop installation or the build entirely. Worse, the error message usually comes from the underlying runtime or parser, not from the generator, so it is phrased in terms of the programming language rather than your site. A realistic example: a JSON data file with a trailing comma after its last property makes a Netlify build fail with a parser stack trace that never mentions the actual file in beginner-friendly terms.

Confronted with output like that, many people reasonably decide to hand-write HTML, switch to a hosted CMS, or give up on having a site at all. The last outcome is the real loss. A few habits reduce the odds of reaching it:

  • run the build locally with the development server before pushing, so failures appear on your own screen first
  • change one thing at a time, so the most recent edit is the obvious suspect when something breaks
  • read the error from the bottom up and look for a filename and line number, which is usually the real clue
  • validate JSON and YAML with an editor extension or linter, since those formats cause a large share of beginner build errors
  • commit working states often, so you can always return to the last version that built

Learning by modifying a starter

A proven way to learn a generator is to take a ready-made starter or theme, get it building, and then change it piece by piece until you understand every file. Eventually you know enough to write your own from scratch. Good starters for this purpose share a few properties: clear documentation, a small number of files, and a single obvious place for content and for settings. Typical examples look like this:

  • a Hugo starter where posts go in /post and site customisation happens in hugo.toml, optionally with IndieWeb features such as microformats2 and an h-card prebuilt
  • an Eleventy starter where posts go in /posts and site details live in a data file like site.js
  • a Jekyll starter where posts go in /_posts and settings live in _config.yml

Deliberately plain starters are an advantage for learning. When the styling is minimal, the structure is easy to see, and all of the design work is left for you to make your own.

Tiny generators with almost no moving parts

Hugo, Eleventy and Jekyll are the popular choices, but a whole family of very small generators exists for people who want to understand the entire tool in an afternoon.

  • barf, short for "blogs are really fun", is a roughly 170-line shell script by btxx, forked from Karl Bartel's blog.sh. It has no front matter and no templating. You write Markdown files, run make build, and upload the resulting build/ folder with rsync. RSS feeds are generated for you, the script works natively on OpenBSD, macOS and Linux, and its stylesheet is four lines long. The README and a live demo show what the result looks like.
  • bashblog is a single script, bb.sh, of about 1,000 lines with no dependencies beyond standard Unix utilities like date, grep, sed and head. Carlos Fenollosa wrote the first version in 2011 and explained the approach in a blog post back then; it was still maintained at the time of writing. Once bb.sh sits in your server's public directory, ./bb.sh post opens a new entry. Drafts, tags, Markdown and RSS work without any installation step. A community fork, bashblog-ng, adds more features.
  • kiki, by vga256, calls itself a tiny homepage construction kit with a small footprint. It is written in PHP rather than shell, can run either as a live dynamic site or as a static generator, and can also act as a public wiki or, in early form, a Gopher hole. It is about 1,500 lines of hand-written code with no JavaScript and no external dependencies. It is shareware, free with a footer credit or available with extra features for a one-time fee of 15 CAD at the time of writing. It suits hosts that support PHP but offer no shell access.

A few more worth knowing:

  • ssg is a POSIX-compliant shell script by Roman Zolotarev that inspired several tools in this list; pyssg is a Python rewrite of it.
  • sw, written in C, is a deliberately minimal web framework, and its fork simple-static reduces it further to what its README describes as the simplest static site generator its maintainer could imagine.
  • makesite.py is a Python counterpart to barf and bashblog, under 130 lines, built by Sunaina Pai on the principle that the code itself is the documentation. There is no configuration layer; you read the script and edit it directly.

None of these approach the feature set of Hugo or Eleventy, and that is the point. What they give up in plugins and template formats they repay in transparency: when something breaks, the whole program fits on your screen.

If you are open to stepping outside the web entirely, publishing on the Gemini protocol is another option. Gemini pages use a simple text format served as-is, so there is often nothing to generate at all.

Key takeaways

  • Learn to write a page by hand first; a generator automates repetition you should already recognise.
  • Most SSG confusion is vocabulary. Once source, output, build, layout, partial and front matter are clear, the documentation of every tool reads similarly.
  • Layouts, partials and global data files exist so that a change made once appears everywhere on the next build.
  • Git provides history and a publishing route, but rsync, FTP or a browser upload are legitimate alternatives for a personal site.
  • Build failures are usually small syntax errors surfaced through unfriendly runtime messages. Build locally, change one thing at a time and validate your data files.
  • A popular generator is not mandatory. A 170-line shell script or a single PHP file can run a perfectly good blog, and the best tool is the one that gets your writing online.