I've spent years working with semantic HTML and CSS, building websites where markup carries meaning and stylesheets follow logical hierarchies. When I needed a static site generator that respected these principles whilst staying within Python's ecosystem, I found myself caught between tools that either forced me into Node.js workflows or buried semantic concerns beneath layers of abstraction.
That gap led to MintyFlaskSSG.
The semantic web heritage
The semantic web movement taught us to think carefully about markup. An <article> isn't just a <div> with styling—it carries meaning about document structure. A <nav> explicitly declares navigation purpose. This matters for accessibility, for search engines, for maintainability, and frankly, for the craft of web development itself.
Modern frameworks often treat HTML as an implementation detail, something to generate rather than author. Whilst I understand the appeal of component-based architectures, I miss the directness of writing semantically meaningful markup and knowing exactly what reaches the browser.
MintyFlaskSSG embraces this heritage. Templates are Jinja2 files containing actual HTML—markup you can read, understand, and reason about. The three-layer template system (theme → mixins → core) provides composition without sacrificing clarity. When you extend a base template, you're working with real HTML structure, not virtual DOM abstractions.
Content from YAML and Markdown
At the heart of MintyFlaskSSG sits a simple principle: structured data belongs in YAML, flowing prose belongs in Markdown, and never shall the two mix improperly.
Site configuration and i18n data live in site.yaml:
site:
contact_details:
main:
name: Your Company
email: hello@example.com
i18n:
en:
site_name: My Site
navigation:
- nav-item:
title: About
url: /about
fr:
site_name: Mon Site
navigation:
- nav-item:
title: À propos
url: /a-propos
This YAML structure flows directly into templates via context processors. No API calls, no database queries—just data that Jinja2 accesses naturally:
<title>{{ i18n_yaml.site_name }}</title>
{% for nav in navigation %}
{% set item = nav['nav-item'] %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
Meanwhile, blog posts and pages remain pure Markdown with minimal frontmatter:
---
title: "Article Title"
date: 2025-08-10
category: technology
tags: [flask, python]
status: published
---
Content written in Markdown...
This separation feels right. YAML defines structure and configuration. Markdown carries narrative and content. Flask-FlatPages bridges them, and Jinja2 renders the results. Each component does what it does best.
The split: themes as a separate concern
MintyFlaskSSG started life as a monolithic project that included both the static site generator and a collection of themes. This worked initially, but conflated two distinct concerns: content management and presentation.
The split into MintyFlaskSSG and MintyFlaskThemes recognised this distinction. MintyFlaskSSG handles content processing, routing, and build mechanics—the engine. MintyFlaskThemes provides the three-layer template architecture and pre-built themes—the aesthetics.
This separation brings practical benefits. You can develop themes independently of the core SSG. You can version them separately. Most importantly, you can mix and match—use MintyFlaskSSG with custom themes, or adapt MintyFlaskThemes' patterns to other Flask applications.
The two projects communicate through well-defined interfaces: template directories, static file routes, and Jinja2 globals. MintyFlaskSSG expects themes in specific locations and provides template functions like theme() and theme_static(). MintyFlaskThemes supplies the directories and follows the conventions. Clean separation, clear contracts.
Flask patterns, Pelican inspiration
I've always admired Pelican's approach to static site generation. It understands that developers want structure without constraint, convention without rigidity. Pelican processes content during build time, generates clean HTML, and gets out of your way.
MintyFlaskSSG takes this philosophy but implements it through Flask patterns I already know: app factories, blueprints, context processors, template filters. If you've built Flask applications, you understand MintyFlaskSSG's architecture immediately.
The app factory in create_app() configures extensions, registers blueprints, and sets up template resolution:
def create_app():
app = Flask(__name__)
# Load configuration
app.config.from_object(Config)
Config.init_app(app)
# Initialise extensions
init_flatpages(app)
# Register blueprints
app.register_blueprint(blog_bp)
app.register_blueprint(home_bp)
app.register_blueprint(docs_bp)
return app
Blueprints provide modularity. The blog blueprint handles articles, categories, tags, and archives. The photos blueprint manages gallery processing and image optimisation. The docs blueprint serves documentation with project-specific navigation. Each blueprint remains self-contained—add them as needed, remove them if you don't.
Build time happens through Frozen-Flask, which walks your Flask routes and generates static HTML. This means you develop with Flask's comfortable development server (flask run), then freeze to static files when ready (flask freeze). The workflow feels natural if you've built Flask applications before.
Starter kit and foundation
MintyFlaskSSG occupies an interesting position. For simple websites—a blog, a portfolio, a documentation site—you can use it essentially as-is. Configure site.yaml, write Markdown content, choose a theme, run flask freeze, deploy. This works.
But the real value lies in extensibility. Because MintyFlaskSSG follows standard Flask patterns, extending it feels natural. Need a contact form that sends email? Add a blueprint. Want to integrate with an API? Write a new route module. Require custom content processing? Extend Flask-FlatPages' renderer.
The three-layer template system accommodates this flexibility. Mixins provide reusable functionality (blog features, photo galleries, documentation) without forcing dependencies. Themes handle presentation without touching business logic. Core templates provide the foundation everything builds upon.
This is deliberate. I didn't want another opinionated framework that demands you work its way. I wanted a foundation that respects Flask conventions, embraces semantic HTML, processes content sensibly, and lets you extend in predictable ways.