Generating static pages for a personal blog is not complicated if the workflow is kept clear. The process can be broken into a straightforward pipeline: collect Markdown files and site configuration, organize that data and feed it into templates, then render HTML and write the result into a static output directory.
The most basic way to write an HTML file
At the simplest level, Python can inject values into an HTML string with standard string formatting. If all you need is to place a title and some body content into a page, that alone is enough:
title = "My Title"
html_template = "<html><head><title>{0}</title></head><body>{1}</body></html>"
body = "<h1>hello world</h1>"
with open("file.html", "w") as f:
f.write(html_template.format(title, body))
Here, title is set to My Title, while html_template contains placeholders {0} and {1} for the title and body. The body variable holds a simple heading. When the file is opened in write mode and format() is applied, Python produces a complete HTML document and saves it as file.html in the current directory.
The generated file looks like this:
<html><head><title>My Title</title></head><body><h1>hello world</h1></body></html>
That is already a minimal template system.
Why a real template engine is better
A blog quickly grows beyond one or two variables in a single page. Real pages are built from collections of data, and the rendered HTML usually needs consistent layout, shared sections, and front-end styling. For that, a stronger template system is a much better fit.
Jinja2 is designed for exactly this kind of work. It is fast, expressive, and extensible, and its template syntax is close enough to Python to stay readable while still being suited for HTML generation.
Installing Jinja2
$ pip install Jinja2
Once installed, there are two essential parts to using Jinja2 for static site output:
- Build the template variables, which become the site context.
- Write the templates themselves, including inheritance, rendering, and finally saving the output.
A minimal Jinja2 setup
The basic configuration is simple. You point Jinja2 at the directory containing your templates, prepare a context dictionary, load the template you want, render it, and write the result to disk.
# 设置jinja模板的所在目录
env = Environment(loader=FileSystemLoader(os.path.join("theme")))
context = {
"title" : "hello",
"body" : "world"
}
tmp = env.get_template("index.html") # 获取对应的模板
sitemap_path = os.path.join('index.html') # 输出静态文件
with open(sitemap_path, mode='w', encoding='utf-8') as f:
f.write(tmp.render(**context))
This is enough to render a basic static page, and the same pattern can be reused for all of the site’s output.
Planning templates before writing them
Before writing Jinja2 templates, it helps to decide what kinds of pages the blog will contain. Most blog pages share a common structure, so the layout should come first. A layout.html base template can hold the repeated parts of the page, while child templates fill in the parts that change.
That separation matters. Repeated HTML should not be copied into multiple pages when inheritance and includes can handle it more cleanly.
Core Jinja2 syntax
Jinja2 syntax is compact and easy to remember:
- Statements use
{% %} - Variables use
{{ }} - Comments use
{# #}
For example, outputting a title:
<h1>{{ title }}</h1>
Looping through tags:
{% for tag in tags %}
<samll>{{ tag }}</samll>
{% endfor %}
For more complex pages, multiple levels of inheritance are often useful, while fixed sections should be handled through reusable references.
A block in a base template might look like this:
<title>{{ blog_name_en }}-{{ blog_name }}{% block title %}{% endblock %}</title>
And a child template can extend that layout and override just the title block:
{% extends "layout.html" %}
{% block title %}-{{ title }}{% endblock %}
{# 此处省略了一些代码 #}
With this structure, shared layout stays centralized and page-specific content remains easy to manage.
Putting the pieces together
Once these parts are in place, Python can already generate a set of simple HTML pages: collect content and configuration, prepare the context data, render with Jinja2, and write the final files into the static directory.
From there, the next step is not the idea itself, but the implementation details: how to organize these functions in Python so that page generation becomes part of a complete static blog workflow.