Markdown blog on Flask with Vite-built assets and WeasyPrint PDF export, rewritten from the Wagtail build that preceded it.
blogdockerflaskmarkdownpythonself-hostedvite
1# CLAUDE.md
2
3This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
5## What This Is
6
7A personal blog (blog.bythewood.me) built as a Flask app that renders markdown files. No database — blog posts are `.md` files in `content/posts/` with YAML frontmatter. Uses WeasyPrint for PDF export and Mistune for markdown rendering.
8
9## Commands
10
11- **Dev server:** `make run` (runs Vite watch + Flask dev server concurrently)
12- **Build frontend:** `make build` (or `bun run build`)
13- **Docker build:** `sudo docker build .`
14
15There are no tests or linters configured.
16
17## Architecture
18
19**Backend:** Single-file Flask app (`app.py`). Posts are loaded from `content/posts/*.md` at startup (reloaded per-request in debug mode). Each post has frontmatter fields: title, slug, date, publish_date, tags, description, cover_image. Posts with `publish_date` in the future are hidden.
20
21**Frontend pipeline:** Vite builds `static_src/` → `static/`. Entry point is `static_src/index.js` which imports SCSS and JS. Output filenames are content-hashed (`base-[hash].js`, `base-[hash].css`) and a Vite manifest (`static/.vite/manifest.json`) is read at runtime so templates can resolve the hashed names for cache busting. Uses Bootstrap 5, CodeMirror (syntax highlighting in posts), and Monaspace Argon font.
22
23**Templates:** Jinja2 templates in `templates/`. `base.html` is the layout. Blog post content is rendered through a custom `BlogRenderer` (Mistune) that wraps blocks in `div.block-*` classes. A separate `PDFRenderer` exists for the PDF export route.
24
25**Content:** `content/posts/` for markdown posts, `content/images/` for images served at `/content/images/`.
26
27## Tooling
28
29- **Python deps:** managed with `uv` (see `pyproject.toml`, `uv.lock`)
30- **JS deps:** managed with `bun` (see `package.json`, `bun.lock`)
31- **Production:** Docker (Alpine-based) + Gunicorn, deployed via `docker-compose`
32
33## Key Routes
34
35- `/posts/<slug>/` — single post (old `/blog/<slug>/` 301-redirects here)
36- `/posts/<slug>/pdf/` — PDF export via WeasyPrint
37- `/posts/<slug>/md/` — raw markdown download
38- `/blog/` — post index (also `/blog/tag/<tag>/` and `/blog/year/<year>/`)
39- `/search/live/` — JSON endpoint for live search
40- `/og/<slug>.svg` — dynamic OG image generation