7. Layouts and static files

As soon as your app has more than one page, two problems appear: every page repeats the same HTML shell, and every page needs the same CSS and JavaScript. Flask and Jinja2 solve both. Template inheritance lets you write the shared shell once and fill in only what changes per page, and url_for() serves your static files without brittle hand-written paths.

The repetition problem

Without a shared layout, every template carries its own <html>, <head>, stylesheet link, and navigation bar. Change the navigation once and you are editing five files by hand, with five chances to get it wrong. Template inheritance removes the copy-paste: one parent template owns the shell, and each page declares only its own content.

A base layout with extends and block

The parent template defines the page shell and marks the spots a child can fill with {% block %}. Save it as templates/base.html:

templates/base.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{% block title %}My Site{% endblock %}</title>
  <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
  {% include "includes/nav.html" %}
  <main>
    {% block content %}{% endblock %}
  </main>
</body>
</html>

A child page uses {% extends %} to inherit the shell, then overrides only the blocks it cares about. Everything outside a block (the <head>, the navigation, the closing tags) comes from the parent automatically:

templates/home.html
{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
  <h1>Welcome</h1>
  <p>This template only defines what is unique to this page.</p>
{% endblock %}

Reusable pieces with include

Inheritance shares the whole shell; {% include %} shares a single component. Write the navigation once and pull it into the layout. Save it as templates/includes/nav.html:

templates/includes/nav.html
<nav>
  <a href="{{ url_for('home') }}">Home</a>
</nav>

Because base.html already includes it, every page that extends the base gets the navigation for free. Add more links as you add more routes; every name you pass to url_for() must match a real view function. Edit one file and all pages update.

Serving static files with url_for()

Flask serves anything in a folder named static/ at the URL /static/.... So static/css/style.css is reachable at /static/css/style.css. The folder layout is fixed by convention:

Project layout
my-app/
├── app.py
├── templates/
│   ├── base.html
│   └── includes/nav.html
└── static/
    ├── css/style.css
    └── js/app.js

You saw the link already in base.html: {{ url_for('static', filename='css/style.css') }}. It is tempting to hardcode /static/css/style.css instead, and it would even work at first. Prefer url_for() anyway: it builds the correct path relative to wherever the app is mounted. If the app later moves under a sub-path, the generated link can move with it. Hardcoded paths break silently the day that changes.

Cache-busting in one line

Browsers cache CSS and JavaScript aggressively, which is good for speed but means a returning visitor can keep seeing an old stylesheet after you ship a fix. Appending a version query string forces a fresh download when the value changes:

templates/base.html
<link rel="stylesheet"
      href="{{ url_for('static', filename='css/style.css') }}?v=2">

Bump v when the file changes and every browser fetches the new version. That is enough for a single-developer project; larger setups automate the version from the file's modification time, but the principle is the same.