Skip to main content

A lightweight, elegant blogging platform built with FastHTML

Project description


title: Bloggy - A FastHTML Blogging Platform

Bloggy

A lightweight, elegant blogging platform built with FastHTML that renders Markdown files into beautiful web pages with advanced features.

Architecture Overview

---
width: 80vw
---
graph TB
    subgraph "User Interface"
        Browser[Web Browser]
        Theme[Light/Dark Theme Toggle]
    end
    
    subgraph "FastHTML Application"
        App[FastHTML App<br/>core.py]
        Router[URL Router]
        Layout[Layout Engine]
        
        subgraph "Route Handlers"
            Index[Index Route<br/>/]
            PostDetail[Post Detail Route<br/>/posts/path]
        end
    end
    
    subgraph "Markdown Processing"
        MDParser[Mistletoe Parser]
        Renderer[ContentRenderer]
        
        subgraph "Custom Renderers"
            Footnotes[Footnote Renderer<br/>Sidenotes]
            Mermaid[Mermaid Diagram Renderer<br/>Zoom/Pan Controls]
            Links[Link Renderer<br/>HTMX Integration]
        end
    end
    
    subgraph "File System"
        MDFiles[Markdown Files<br/>.md]
        Tree[Folder Tree Builder<br/>build_post_tree]
    end
    
    subgraph "Frontend Assets"
        Static[Static Files]
        JS[scripts.js<br/>Mermaid + Interactions]
        CSS[Styles<br/>TailwindCSS + MonsterUI]
    end
    
    Browser -->|HTTP Request| Router
    Theme -->|Toggle Dark Mode| JS
    
    Router --> Index
    Router --> PostDetail
    
    Index --> Tree
    Tree --> MDFiles
    Index --> Layout
    
    PostDetail --> MDFiles
    PostDetail --> MDParser
    
    MDParser --> Renderer
    Renderer --> Footnotes
    Renderer --> Mermaid
    Renderer --> Links
    
    Footnotes -->|Marginal Notes| Layout
    Mermaid -->|Interactive Diagrams| Layout
    Links -->|HTMX Navigation| Layout
    
    Layout --> Browser
    
    JS -->|Theme Change| Mermaid
    JS -->|Zoom/Pan/Reset| Mermaid
    
    Static --> CSS
    Static --> JS
    
    App -.->|Serves| Static
    
    style Browser fill:#e1f5ff
    style App fill:#fff3cd
    style MDParser fill:#d4edda
    style Static fill:#f8d7da
    style Mermaid fill:#cce5ff
    style Footnotes fill:#cce5ff

How Bloggy Works

1. Request Flow

---
width: 80vw
---
sequenceDiagram
    participant User
    participant Browser
    participant FastHTML
    participant Router
    participant FileSystem
    participant Renderer
    participant HTMX
    
    User->>Browser: Visit /
    Browser->>FastHTML: GET /
    FastHTML->>Router: Route to index()
    Router->>FileSystem: Scan for .md files
    FileSystem-->>Router: Return file tree
    Router->>Browser: Render post list + layout
    
    User->>Browser: Click post link
    Browser->>HTMX: hx-get="/posts/demo"
    HTMX->>FastHTML: GET /posts/demo
    FastHTML->>Router: Route to post_detail()
    Router->>FileSystem: Read demo.md
    FileSystem-->>Router: Markdown content
    Router->>Renderer: Parse & render markdown
    
    rect rgb(200, 220, 250)
        Note over Renderer: Process custom syntax:<br/>- Footnotes [^1]<br/>- Mermaid diagrams<br/>- Internal links
    end
    
    Renderer-->>Router: Rendered HTML
    Router->>HTMX: Return HTML fragment
    HTMX->>Browser: Swap content (#main-content)
    Browser->>User: Display post

2. Markdown Processing Pipeline

---
width: 80vw
---
flowchart LR
    A[Raw Markdown] --> B{Extract Footnotes}
    B -->|Content| C[Preprocess Super/Sub]
    B -->|Footnote Defs| D[Store in Dict]
    
    C --> E[Preprocess Tabs]
    E -->|Content + Placeholders| F[Mistletoe Parser]
    E -->|Tab Data| G[Tab Store]
    
    F --> H[Token Stream]
    
    H --> I{ContentRenderer}
    
    I -->|BlockCode + 'mermaid'| J[Mermaid Renderer]
    I -->|Link| K[Link Renderer]
    I -->|FootnoteRef| L[Footnote Renderer]
    I -->|Other| M[Standard HTML]
    
    J --> N[Diagram with Controls]
    K --> O{Relative/Internal?}
    O -->|Relative| P[Resolve Path]
    P --> Q[Add HTMX attrs]
    O -->|Internal| Q
    O -->|External| R[Add target=_blank]
    
    L --> S[Sidenote Component]
    D --> S
    
    N --> T[Initial HTML]
    Q --> T
    R --> T
    S --> T
    M --> T
    
    T --> U[Postprocess Tabs]
    G --> U
    U -->|Render Each Tab| V[ContentRenderer]
    V --> U
    
    U --> W[Apply CSS Classes]
    W --> X[Final HTML]
    
    style J fill:#ffe6cc
    style L fill:#d1ecf1
    style O fill:#fff3cd
    style U fill:#e7d4ff

3. Mermaid Diagram Lifecycle

---
width: 60vw
---
stateDiagram-v2
    [*] --> Rendered: Page Load
    
    state Rendered {
        [*] --> Initialize
        Initialize --> AddControls: Create buttons
        AddControls --> StoreCode: Save original code
        StoreCode --> EnableInteraction: Mouse events
    }
    
    state EnableInteraction {
        [*] --> Idle
        Idle --> Panning: Mouse drag
        Idle --> Zooming: Mouse wheel
        Idle --> ButtonZoom: +/- buttons
        ButtonZoom --> Idle
        Zooming --> Idle
        Panning --> Idle
        
        state "Reset Button" as Reset
        Idle --> Reset: Click reset
        Reset --> Idle: Restore defaults
    }
    
    Rendered --> ThemeChange: Dark/Light toggle
    
    state ThemeChange {
        [*] --> DetectTheme
        DetectTheme --> GetOriginalCode: Read data attribute
        GetOriginalCode --> ClearWrapper
        ClearWrapper --> ReinitMermaid: New theme
        ReinitMermaid --> ReRender: mermaid.run()
    }
    
    ThemeChange --> Rendered: Re-rendered
    
    note right of ThemeChange
        MutationObserver watches
        HTML class changes
    end note
    
    note right of EnableInteraction
        Transform state stored
        per diagram ID
    end note

Key Features

✨ Advanced Markdown Features

  • Footnotes as Sidenotes: [^1] references become elegant margin notes on desktop, expandable on mobile
  • Mermaid Diagrams: Full support for flowcharts, sequence diagrams, state diagrams, etc.
  • Interactive Diagrams: Built-in zoom, pan, and reset controls for all mermaid diagrams
  • Theme-aware Rendering: Diagrams automatically re-render when switching light/dark mode
  • Tabbed Content: Create multi-tab sections using :::tabs syntax for comparing code, showing examples, etc.
  • Relative Links: Full support for relative markdown links (./file.md, ../other.md) that work seamlessly with navigation
  • Math Notation: KaTeX support for inline $E=mc^2$ and block $$ math equations
  • Superscript & Subscript: Use ^text^ for superscript and ~text~ for subscript
  • Pandoc-style Attributes: Add classes to inline text with `text`{.class} syntax for semantic markup (renders as <span> tags)
  • Cascading Custom CSS: Add custom.css files at multiple levels (root, folders) for flexible styling

🎨 Modern UI

  • Responsive Design: Works beautifully on all screen sizes
  • Three-Panel Layout: Posts sidebar, main content, and table of contents for easy navigation
  • Dark Mode: Automatic theme switching with localStorage persistence
  • HTMX Navigation: Fast, SPA-like navigation without full page reloads
  • Collapsible Folders: Organize posts in nested directories
  • Auto-Generated TOC: Table of contents automatically extracted from headings

🚀 Technical Highlights

  • Built on FastHTML for modern Python web development
  • Uses Mistletoe for extensible Markdown parsing with custom renderers
  • TailwindCSS + MonsterUI for styling
  • Hyperscript for interactive behaviors
  • Mermaid.js v11 for diagram rendering with custom controls
  • KaTeX for mathematical notation rendering
  • Smart Link Resolution: Automatically converts relative links to proper routes

Project Structure

graph LR
    subgraph bloggy/
        A[__init__.py]
        B[core.py<br/>Main App Logic]
        C[main.py<br/>Entry Point]
        
        subgraph static/
            D[scripts.js<br/>Mermaid + Interactions]
            E[sidenote.css<br/>Footnote Styles]
            F[favicon.png]
        end
    end
    
    subgraph demo/
        G[*.md Files<br/>Your Blog Posts]
        
        subgraph guides/
            H[*.md Files<br/>Nested Content]
        end
    end
    
    B --> D
    B --> E
    B --> F
    B -.reads.-> G
    B -.reads.-> H
    
    style B fill:#ffe6cc
    style D fill:#d1ecf1
    style G fill:#d4edda

Installation

From PyPI (recommended)

pip install bloggy

From source

git clone https://github.com/yeshwanth/bloggy.git
cd bloggy
pip install -e .

Quick Start

  1. Create a directory with your markdown files:

    mkdir my-blog
    cd my-blog
    echo "# Hello World" > hello.md
    
  2. Run Bloggy:

    bloggy .
    
  3. Open your browser at http://127.0.0.1:5001

Configuration

Bloggy supports three ways to configure your blog (in priority order):

  1. .bloggy configuration file (TOML format)
  2. Environment variables
  3. Default values

Using a .bloggy Configuration File

Create a .bloggy file in your blog directory or current directory:

# Blog title (default: derives from root folder name)
title = "My Awesome Blog"

# Root folder containing markdown files (default: current directory)
root = "."

# Server host (default: 127.0.0.1)
# Use "0.0.0.0" to make the server accessible from network
host = "127.0.0.1"

# Server port (default: 5001)
port = 5001

All settings in the .bloggy file are optional. See .bloggy.example for a full example.

Environment Variables

You can also use environment variables as a fallback:

  • BLOGGY_ROOT: Path to your markdown files (default: current directory)
  • BLOGGY_TITLE: Your blog's title (default: folder name)
  • BLOGGY_HOST: Server host (default: 127.0.0.1)
  • BLOGGY_PORT: Server port (default: 5001)

Examples

Using a .bloggy file:

# Create a .bloggy file in your blog directory
cd /path/to/your/blog
cat > .bloggy << EOF
title = "My Tech Blog"
port = 8000
EOF

bloggy

Using environment variables:

export BLOGGY_ROOT=/path/to/your/markdown/files
export BLOGGY_TITLE="My Awesome Blog"
export BLOGGY_PORT=8000
bloggy

Passing directory as argument:

bloggy /path/to/your/markdown/files

Configuration priority example:

If you have both a .bloggy file with port = 8000 and an environment variable BLOGGY_PORT=9000, the .bloggy file takes precedence and port 8000 will be used.

Custom Styling with Cascading CSS

Bloggy supports cascading custom CSS at multiple levels, allowing you to style your entire blog globally or customize specific sections:

CSS Loading Order

  1. Framework CSS (bloggy/static/custom.css) - Core styling for Bloggy itself
  2. Blog-wide CSS (/your-blog-root/custom.css) - Applies to all posts
  3. Folder-specific CSS (/your-blog-root/section/custom.css) - Applies only to posts in that folder

Each level can override styles from previous levels, following standard CSS cascade rules.

Pandoc-style Inline Attributes

Use backticks with attributes to add semantic classes to inline text:

The variables `x`{.variable}, `y`{.variable}, and `z`{.variable} represent coordinates.

Use `important`{.emphasis} for highlighted terms.

The function `console.log()`{.code} prints to console.

Attributes support:

  • Classes: .variable, .emphasis, .keyword
  • IDs: #unique-id
  • Key-value pairs: lang=python

Classes like .variable, .emphasis, and .keyword render as <span> tags (not <code>), making them perfect for semantic styling without monospace fonts.

Example: Multi-level Custom CSS

Root level (/blog/custom.css) - Global styles:

/* Base variable styling for all posts */
span.variable {
    color: #e06c75;
    font-weight: 500;
}

span.emphasis {
    background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
    padding: 2px 6px;
    border-radius: 3px;
}

Section level (/blog/tutorials/custom.css) - Tutorial-specific:

/* Override for tutorial section - use blue variables */
span.variable {
    color: #61afef;
    position: relative;
}

/* Add overline to variables in tutorials */
span.variable::before {
    content: '';
    position: absolute;
    top: -3px;
    left: 50%;
    transform: translateX(-50%);
    width: 80%;
    height: 2px;
    background-color: currentColor;
}

span.emphasis {
    background: #ffd93d;
    color: #333;
    font-weight: bold;
}

Chapter level (/blog/tutorials/advanced/custom.css) - Advanced chapter styling:

/* Different style for advanced tutorials */
span.variable {
    color: #c678dd;
    font-style: italic;
}

/* Add special marker for keywords */
span.keyword {
    color: #e5c07b;
    text-transform: uppercase;
    font-size: 0.85em;
    letter-spacing: 1px;
}

Real Example from Demo

See the demo/flat-land/ folder for a working example:

Markdown (demo/flat-land/chapter-02.md):

The two Northern sides `RO`{.variable}, `OF`{.variable}, constitute the roof.

CSS (demo/flat-land/custom.css):

span.variable {
    color: #e06c75;
    position: relative;
}

span.variable::before {
    content: '';
    position: absolute;
    top: -3px;
    left: 50%;
    transform: translateX(-50%);
    width: 80%;
    height: 2px;
    background-color: currentColor;
}

This renders RO and OF in red with a line above them, perfect for mathematical or geometric notation!

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bloggy-0.1.30.tar.gz (46.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bloggy-0.1.30-py3-none-any.whl (42.1 kB view details)

Uploaded Python 3

File details

Details for the file bloggy-0.1.30.tar.gz.

File metadata

  • Download URL: bloggy-0.1.30.tar.gz
  • Upload date:
  • Size: 46.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for bloggy-0.1.30.tar.gz
Algorithm Hash digest
SHA256 e6722e73ec6dd12625dcefe41b8991e5f0ecb1edbbe085cf9ba8b9f3c52f10d2
MD5 2b96c32de1bf1472301824accdaca35e
BLAKE2b-256 41154bedd67530beb98c22ef60f55566ae9d0add4a9df1fdb130b1a24e378161

See more details on using hashes here.

File details

Details for the file bloggy-0.1.30-py3-none-any.whl.

File metadata

  • Download URL: bloggy-0.1.30-py3-none-any.whl
  • Upload date:
  • Size: 42.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for bloggy-0.1.30-py3-none-any.whl
Algorithm Hash digest
SHA256 5a8133f4b7cffed27fe5af1fad46a1d341d1184647432d7c6073de57bc68a122
MD5 cd7a9476251f3cb9e3fdcc2f6bb90a4d
BLAKE2b-256 0f11cd95a85def80772982eb67d06e336b7ec8c3c5a46cc9377bf45b70b78c75

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page