Jinja2 Templates β Rendering HTML Pages
In this chapter, you will learn to use the Jinja2 template engine to render complete HTML pages and extract common layouts.
From Strings to Templates
In the previous chapter, view functions directly returned strings with HTML tags embedded inline, which could not be reused and were difficult to maintain.
render_template() is Flask's entry point for rendering templates: it loads HTML files, injects variables, and returns complete HTML responses.
Example
# File path: app.py
from flask import Flask, render_template
app = Flask( __name__ )
@app.route("/")
def index():
# render_template('template_filename', variable_name=value, ...)
return render_template('index.html', title='TUTORIAL Blog')
Flask looks for templates in the templates/ folder in the project root directory by default.
Creating Template Files
Create a templates/ folder in the project root directory, then create index.html.
Example
<!-- File path: templates/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
<p>Welcome! This page is rendered with the Jinja2 template engine.</p>
</body>
</html>
Jinja2 Template Syntax Quick Reference
Jinja2 syntax is almost identical to Django template syntax, and also similar to Vue3's interpolation syntax.
| Purpose | Jinja2 / Django | Description |
|---|---|---|
| Output variable | {{ variable }} |
Supports attribute access: {{ user.name }} |
| Loop | {% for item in list %}...{% endfor %} |
Supports loop.index |
| Condition | {% if cond %}...{% endif %} |
Chainable: {% elif %} |
| Inheritance | {% extends 'base.html' %} |
Must be on the first line |
| Block definition | {% block name %}...{% endblock %} |
Child templates fill in slots in parent templates |
Jinja2 and Django template syntax are almost identical, but Jinja2 is more flexible: you can write more complex Python expressions in templates, while Django templates deliberately restrict some capabilities to prevent business logic from leaking into templates.
Template Inheritance: base.html
Multiple pages share navigation bars and footers; use template inheritance to avoid repeating HTML.
Creating the base.html Parent Template
Example
<!-- File path: templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport"content="width=device-width, initial-scale=1.0">
<!-- block title: child templates can override the page title -->
<title>{% block title %}TUTORIAL Blog{% endblock %}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
color: #333;
}
.navbar {
display: flex; justify-content: space-between; align-items: center;
padding: 16px 40px; background: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.navbar .logo { font-size: 22px; font-weight: bold; color: #e74c3c; text-decoration: none; }
.navbar a { margin-left: 20px; text-decoration: none; color: #333; }
.container { max-width: 960px; margin: 40px auto; padding: 0 20px; }
.footer { text-align: center; padding: 20px; color: #999; border-top: 1px solid #eee; }
</style>
{% block extra_head %}{% endblock %}
</head>
<body>
<header class="navbar">
<a href="/"class="logo">TUTORIAL Blog (Flask)</a>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main class="container">
<!-- block content: child templates fill in page main content here -->
{% block content %}
{% endblock %}
</main>
<footer class="footer">
<p>Β© 2024 TUTORIAL Blog. Powered by Flask.</p>
</footer>
</body>
</html>
Child Template Inheriting from Parent
Example
<!-- File path: templates/index.html -->
{% extends 'base.html' %}
{% block title %}TUTORIAL Blog - Home{% endblock %}
{% block content %}
<h2>Latest Articles</h2>
<p>Article list will be shown in subsequent chapters.</p>
{% endblock %}
{% extends 'base.html' %}must be written on the first line of the child template (comments excepted). This is a hard requirement of Jinja2 β the template engine needs to determine the inheritance relationship first.
url_for() Generating Links
Don't hardcode URLs; use url_for() to reverse lookup addresses by function name.
Example
# Define route endpoints in views.py
@app.route("/")
def index(): ...
@app.route("/post/<int:post_id>")
def post_detail(post_id): ...
Example
[mycode4 type="html">
<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('post_detail', post_id=3) }}">Article 3</a>
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Image 1">
When routes change (e.g., /post/ to /article/), url_for() in templates automatically generates new addresses.
Static File Configuration
Flask uses the static/ directory as the static file root directory by default.
blog_project/
βββ static/
β βββ css/
β β βββ style.css
β βββ js/
β β βββ main.js
β βββ images/
β βββ logo.png
βββ templates/
βββ app.py
Reference in templates via url_for('static', filename='css/style.css').
Chapter Summary
In this chapter, you mastered the core of Jinja2 templates: render_template() for rendering templates, {{ }} for outputting variables, {% for/if %} for control flow, base.html + block + extends for template inheritance, and url_for() for generating links.
Now the blog pages have become clearly structured HTML, with common parts extracted into base.html.
YouTip