What a Generator Actually Is, and Setup
Before writing any code, you need the one mental model the entire project rests on. It's small, and once it clicks, every static site tool you'll ever meet - Jekyll, Hugo, Eleventy - stops being mysterious.
Build time vs request time
What a dynamic site does. When someone visits a WordPress blog, a program wakes up at that moment: it queries a database for the post, runs it through a theme, assembles HTML, and sends it back. That work happens on every single request, which is why a dynamic site needs a server running code 24/7, a database, security patches, and a hosting bill.
What a static site generator does. It does the exact same assembly work - read content, apply a template, produce HTML - but once, on your machine, before anything is deployed. The output is a folder of finished .html files. The web host's only job is handing those files out, which is the one thing web servers are effortlessly good at.
That's the whole idea. The trade is explicit: you give up per-visitor behavior (comments, logins, "personalized for you") and in exchange the site is fast, free to host, and has nothing running that can crash or be hacked.
💡 Key point: a static site generator is not a kind of website. It's a program that runs at build time and writes a website out as files. build.py, the script you're about to write, is a static site generator - the same species as Hugo, just smaller.
📝 Terminology: the moment your generator runs is build time. The moment a visitor loads a page is request time. At request time, a static site does nothing at all - the work was already done.
Make the project
Open a terminal, make a folder, and set up an isolated Python environment inside it:
python -m venv .venv creates a virtual environment - a private copy of Python in the .venv folder, so the library we install lands in this project instead of polluting your system Python. Activate it:
# macOS / Linux:
# Windows (PowerShell or cmd):
Your prompt grows a (.venv) prefix - that's how you know pip now installs into the project. You'll need to re-activate in every new terminal you open for this project.
Now install the one dependency:
(.venv) $ pip install markdown
Collecting markdown
Downloading markdown-3.8.2-py3-none-any.whl (106 kB)
Installing collected packages: markdown
Successfully installed markdown-3.8.2
Your version number may differ - that's fine. The markdown package converts Markdown text to HTML. That's the only job we're outsourcing: writing a correct Markdown parser is a multi-week project of its own, and it isn't the thing this build teaches.
Create the folders the generator will read from:
Which gives you this layout:
myblog/
.venv/ <- the virtual environment (never touch it)
build.py <- the generator - you write this next
posts/ <- your writing, one .md file per post
templates/ <- HTML skeletons (phase 3)
static/ <- CSS, copied through untouched (phase 3)
site/ <- the OUTPUT - generated, never edited by hand
site/ doesn't exist yet. The build creates it - and that's a rule worth tattooing somewhere: you never edit anything in site/, because the next build will overwrite it. Source lives in posts/, templates/, and static/; everything in site/ is disposable.
Write two posts
A generator with nothing to generate is hard to test. Posts are Markdown files with a metadata block at the top. Create posts/2026-06-28-hello-world.md:
- --
And a second one, posts/2026-07-02-why-static-sites.md:
The block between the --- lines is called frontmatter - metadata about the post that isn't part of the post. The filename starts with the date so posts sort naturally in a file listing; the rest of the filename will become the post's URL later.
📝 Terminology: frontmatter is the key: value block fenced by --- at the top of a content file. Jekyll popularized it, and now it's everywhere - this platform's own guides use it. You'll write the parser for it in phase 2.
The first build.py
Start the generator with the smallest thing that proves the pipeline: find the posts. Create build.py in the project root:
=
=
Two things here carry through the whole project. Path("posts") is pathlib, the standard library's way of handling files - POSTS_DIR.glob("*.md") means "every .md file in that folder," and it returns them in no guaranteed order, which is why we sorted() them. And the if __name__ == "__main__": guard means "only run main() when this file is executed directly, not when it's imported" - that guard becomes important in phase 5, when another script imports this one.
Run it:
(.venv) $ python build.py
Found 2 post(s):
- 2026-06-28-hello-world.md
- 2026-07-02-why-static-sites.md
What just happened: Python globbed the posts/ folder, found your two files, and listed them. Unimpressive on purpose - but the pipeline's first segment (source folder → script) is real and verified. Everything from here on is teaching this script what to do with those files.
What you have now
A project folder with an isolated environment, one installed library, two honest Markdown posts, and a script that finds them. Next phase, the script learns to actually read a post: split the frontmatter from the body, parse the metadata, and convert the Markdown to HTML - the input half of the generator, done properly.
Quick check that the mental model stuck before you move on:
[
{
"q": "When does a static site generator do its work?",
"choices": ["On every visitor request, like WordPress", "Once, at build time, before anything is deployed", "Continuously, on a schedule in the cloud"],
"answer": 1,
"explain": "The generator runs once on your machine and writes finished HTML files. At request time, the host serves those files as-is - no code runs per visitor."
},
{
"q": "What will the site/ folder hold, and how should you treat it?",
"choices": ["Your Markdown sources - edit freely", "Server configuration files", "The generated output - never edit it by hand, the next build overwrites it"],
"answer": 2,
"explain": "site/ is disposable output. Source of truth lives in posts/, templates/, and static/."
},
{
"q": "What is frontmatter?",
"choices": ["The key: value metadata block between --- lines at the top of a content file", "The first paragraph of a post", "An HTML template header"],
"answer": 0,
"explain": "It carries data about the post (title, date, tags) that isn't part of the post's body text."
}
]
Check your understanding 3 questions
1. When does a static site generator do its work?
2. What will the site/ folder hold, and how should you treat it?
3. What is frontmatter?