Skip to main content

medusa-ssg

Minimal Python static site generator. Markdown + Jinja2, Tailwind CSS built-in, zero configuration, optional frontmatter.

Quick Start

pip install medusa-ssg
medusa new mysite
cd mysite
medusa serve

Open http://localhost:4000 to see your site with live reload.

Features

  • Zero frontmatter required — title, date, tags, and description derived from filenames and content
  • Optional YAML frontmatter — add custom metadata when you need it
  • Markdown + Jinja2 — write content in Markdown, layouts in Jinja2
  • Tailwind CSS — built-in pipeline with automatic processing
  • Live reload — dev server with WebSocket-based hot reload
  • Pretty URLs — /posts/hello/ not /posts/hello.html
  • Automatic sitemap & RSS — generated on build
  • Custom 404 pages — static HTML served with proper status codes
  • Syntax highlighting — Pygments-powered code blocks
  • Table of contents — auto-generated from headings

Requirements

  • Python 3.10+
  • Node.js 16+ (for Tailwind CSS)

Installation

# Install Medusa
pip install medusa-ssg

# Or install from source
git clone https://github.com/yourname/medusa.git
cd medusa
pip install -e .

CLI Commands

medusa new NAME          # Create a new project
medusa build             # Build site to output/
medusa build --drafts    # Include draft content
medusa serve             # Dev server at localhost:4000
medusa serve --port 3000 # Custom port
medusa serve --drafts    # Include drafts in dev
medusa md                # Interactive markdown file creator
medusa --version         # Show version

Project Structure

mysite/
├── site/                    # Content and templates
│   ├── _layouts/            # Page layouts
│   │   └── default.html.jinja
│   ├── _partials/           # Reusable components
│   │   ├── header.html.jinja
│   │   └── footer.html.jinja
│   ├── posts/               # Blog posts
│   │   └── 2024-01-15-hello-world.md
│   ├── index.jinja          # Home page
│   ├── about.md             # Static page
│   └── 404.html             # Custom error page
├── assets/
│   ├── css/main.css         # Tailwind entry point
│   ├── js/main.js
│   └── images/
├── data/                    # YAML data files
│   ├── site.yaml            # Site metadata
│   └── nav.yaml             # Navigation links
├── output/                  # Generated site (git-ignored)
├── medusa.yaml              # Configuration
├── tailwind.config.js
└── package.json

Configuration

Create medusa.yaml in your project root:

# Output directory (default: output)
output_dir: output

# Base URL used to absolutize all links in output (default: empty).
# medusa serve always uses http://localhost:<port> for dev builds.
root_url: https://example.com

# Dev server port (default: 4000)
port: 4000

# WebSocket port for live reload (default: port + 1)
ws_port: 4001

Writing Content

Supported File Types

Medusa processes these file types in the site/ directory:

  • Markdown (.md) — Content files with full Markdown support
  • HTML (.html) — Plain HTML files, processed as pages with pretty URLs
  • Jinja templates (.html.jinja, .jinja) — Templates with Jinja2 syntax

All other file types are ignored.

Markdown Pages

Create .md files anywhere in site/:

# My Page Title

Content goes here. The first `# heading` becomes the page title.

Use #hashtags for tags. #python #tutorial

Dated Posts

Name files with a date prefix for automatic date extraction:

site/posts/2024-01-15-my-post.md  →  /posts/my-post/
site/posts/2024-02-20-another.md  →  /posts/another/

Drafts

Prefix files or folders with _ to mark as draft:

site/posts/_work-in-progress.md   # Draft post
site/_experiments/test.md          # Draft folder

Drafts are excluded from builds unless you use --drafts.

Frontmatter

Add optional YAML frontmatter for custom metadata:

---
author: Jane Doe
featured: true
category: tutorials
---

# My Post Title

Content goes here.

Access frontmatter in templates with {{ frontmatter.author }}.

Templates

Available Variables

Variable Description
current_page The current page object
pages Collection of all pages
tags Map of tag names to page collections
data Merged YAML from data/ directory
frontmatter Current page's YAML frontmatter
url_for(path) Generate URLs with base path
render_toc(page) Generate nested <ul> table of contents
css_path(name) Path to CSS file in assets/css/
js_path(name) Path to JS file in assets/js/
img_path(name) Path to image in assets/images/ (auto-detects extension)
font_path(name) Path to font in assets/fonts/ (auto-detects extension)
pygments_css() CSS for syntax highlighting

Page Object

current_page.title        # Page title
current_page.content      # Rendered HTML
current_page.body         # Raw markdown/source text
current_page.description  # First paragraph (for SEO)
current_page.excerpt      # Full first paragraph
current_page.url          # URL path (/posts/hello/)
current_page.slug         # URL slug (hello)
current_page.date         # Publication date
current_page.tags         # List of tags
current_page.toc          # List of headings for TOC
current_page.draft        # Is draft?
current_page.layout       # Layout name
current_page.group        # Folder group (posts, pages, etc.)
current_page.frontmatter  # YAML frontmatter dict

Collections API

{# Get posts #}
{% for post in pages.group("posts").sorted() %}
  <a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}

{# Filter by tag #}
{% for post in pages.with_tag("python").latest(5) %}
  {{ post.title }}
{% endfor %}

{# Published only (excludes drafts) #}
{% for page in pages.published() %}
  {{ page.title }}
{% endfor %}

Sorting

Pages are sorted by three criteria in order:

  1. Date — Newest first (from filename or file modification time)
  2. Number prefix — If dates are equal, by number prefix in filename
  3. Filename — If dates and numbers are equal, alphabetically

Examples of number prefixes:

site/docs/01-introduction.md   →  Sorted first
site/docs/02-getting-started.md →  Sorted second
site/docs/03-advanced.md       →  Sorted third

You can also combine date and number prefixes:

site/posts/2024-01-15-01-part-one.md
site/posts/2024-01-15-02-part-two.md

Partials

{# site/_partials/card.html.jinja #}
<div class="card">
  <h3>{{ title }}</h3>
  <p>{{ description }}</p>
</div>

{# Include in any template #}
{% include "card.html.jinja" %}

Data Files

YAML files in data/ are available in templates:

# data/site.yaml - merged into data root
title: My Site
author: Jane Doe

# data/social.yaml - available as data.social
- platform: GitHub
  url: https://github.com/username
- platform: Twitter
  url: https://twitter.com/username
<h1>{{ data.title }}</h1>
<p>By {{ data.author }}</p>

{% for link in data.social %}
  <a href="{{ link.url }}">{{ link.platform }}</a>
{% endfor %}

Static Files

HTML Pages

HTML files (.html) in site/ are processed as pages with pretty URLs. For example:

  • site/about.html → /about/
  • site/404.html → /404/

The content is wrapped in your layout template like Markdown pages.

404 Page

Create site/404.html for a custom error page. It will be processed as a page at /404/ and served with a 404 status code by the dev server.

Asset Helpers

Reference assets in your templates:

<link rel="stylesheet" href="{{ css_path('main') }}">
<script src="{{ js_path('app') }}"></script>
<img src="{{ img_path('logo') }}" alt="Logo">

All helpers respect root_url for CDN support. Image and font helpers auto-detect file extensions when omitted.

Syntax Highlighting

Code blocks with language identifiers get Pygments highlighting:

```python
def hello():
    print("Hello!")
```

Include the CSS in your layout:

<style>{{ pygments_css() }}</style>

Deployment

Netlify

Create netlify.toml:

[build]
  command = "pip install -e . && medusa build"
  publish = "output"

[[redirects]]
  from = "/*"
  to = "/404.html"
  status = 404

Vercel

Create vercel.json:

{
  "buildCommand": "pip install -e . && medusa build",
  "outputDirectory": "output"
}

GitHub Pages

Create .github/workflows/deploy.yml:

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: pip install medusa-ssg
      - run: npm install
      - run: medusa build
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./output

Manual / Any Host

medusa build
# Upload contents of output/ to your server

Development

# Clone and install
git clone https://github.com/yourname/medusa.git
cd medusa
pip install -e ".[dev]"

# Run tests
pytest

# Run with coverage
pytest --cov=medusa --cov-report=term-missing

License

MIT License. See LICENSE for details.

Metadata

Release files for medusa-ssg 0.1.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for medusa-ssg 0.1.9
File Size Uploaded
medusa_ssg-0.1.9.tar.gz 65.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for medusa-ssg 0.1.9
File Interpreter ABI Platform
medusa_ssg-0.1.9-py3-none-any.whl Python 3 none any Details

Total release size: 119.3 kB

Release files / medusa_ssg-0.1.9.tar.gz

Download URL medusa_ssg-0.1.9.tar.gz
Size 65.4 kB
Tags Source
SHA-256 checksum
How to use checksums
7d7e1072d1cda4caa8b030d8de8a10da65b4ab66f33f6e908876a00bbba45c53
BLAKE2b-256 checksum
How to use checksums
5105d9943b876f71a9aacafc1233ef35d5fafddde7843c05efd53dede024d6be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.0

Release files / medusa_ssg-0.1.9-py3-none-any.whl

Download URL medusa_ssg-0.1.9-py3-none-any.whl
Size 53.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3d65a1e734473f178091d5341d739f8e80ff3b1ef5f37df5de50dfa3f48030bd
BLAKE2b-256 checksum
How to use checksums
326d827f3946c5c6483e5703ac45eb94b8741537f581f1e466efd8b040c30387
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.0

Release history Release notifications | RSS feed

This release

0.1.9 This release

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page