Last updated: June 2026
Architecture Overview¶
The Stack in Layers¶
┌─────────────────────────────────────────────────────────────────────┐
│ Caddy (TLS termination, reverse proxy, rate limiting) │
├─────────────────────────────────────────────────────────────────────┤
│ Uvicorn (ASGI server, async Python event loop) │
├─────────────────────────────────────────────────────────────────────┤
│ Django 6 (the brain) │
│ Routing · Views · ORM · Templates · Admin · Cache │
├─────────────────────────────────────────────────────────────────────┤
│ PostgreSQL (persistent data) │
├─────────────────────────────────────────────────────────────────────┤
│ HTMX (the nervous system) │
│ Partial page updates without a full SPA │
├─────────────────────────────────────────────────────────────────────┤
│ Bootstrap 5 + minimal CSS & JS (the body) │
│ Responsive layout · Theme toggle · Interactions │
└─────────────────────────────────────────────────────────────────────┘
The guiding principle: the server assembles HTML, the browser displays it. No JavaScript framework, no hydration, no client-side routing. HTMX adds interactivity by making the server return HTML fragments instead of JSON.
Project Structure¶
"The tree below is sorted by logical grouping, not alphabetically — directories and files are ordered by purpose and dependency to make the architecture easier to read at a glance."
personalhub/
│
├── config/ # Project config — NOT an app
│ ├── settings/
│ │ ├── __init__.py # empty — makes settings/ a Python package
│ │ ├── base.py # shared across all environments
│ │ ├── dev.py # DEBUG, SQLite, console email, debug-toolbar
│ │ ├── staging.py # staging environment (optional)
│ │ └── prod.py # PostgreSQL, HTTPS headers, security settings
│ ├── urls.py # Root URL dispatcher
│ ├── asgi.py # ASGI entry point (uvicorn target)
│ └── wsgi.py
│
├── apps/
│ ├── core/ # Phase 1: home, about, work, contact
│ ├── projects/ # Phase 2: Project + Tag models
│ └── blog/ # Phase 3: Post model, writing interface
│
├── templates/
│ ├── base.html # Master layout
│ ├── components/ # _nav.html, _footer.html, _alerts.html, _toasts.html
│ ├── 4xx_base.html # Shared error page layout
│ └── ... # 400.html, 403.html, 404.html, 500.html
│
├── static/
│ ├── scss/ # Bootstrap overrides (custom.scss)
│ ├── css/
│ ├── js/
│ └── img/
│
├── locale/ # i18n .po / .mo files
├── docs/ # MkDocs source (you are here)
├── site/ # MkDocs build output (gitignored)
├── scripts/
│ └── dev.sh # Launches Django + MkDocs simultaneously
│
├── manage.py # Django CLI entry point
├── pyproject.toml # uv: dependencies + tool config (ruff, etc.)
├── uv.lock # Committed lockfile — guarantees reproducible installs
├── .python-version # Pins Python 3.14 — read by uv and mise
├── Dockerfile # Multi-stage production image
├── docker-compose.yml # Full production stack (web + db + redis + caddy)
├── .env # Secret values — gitignored
└── .env.example # Committed template for .env
Request Lifecycle¶
A request to /it/work/ travels this path:
Browser
→ Cloudflare (CDN / DDoS layer)
→ Caddy (TLS termination, reverse proxy)
→ Uvicorn (ASGI server)
→ Django middleware stack (security, locale, CSRF, ...)
→ config/urls.py (root dispatcher)
→ i18n_patterns detects 'it' prefix, sets language
→ apps/core/urls.py
→ work view (async)
→ ORM → PostgreSQL
→ Django Template → full HTML response
For an HTMX partial request (e.g. filtering projects by tag), the response is an
HTML fragment (templates/partials/_project_grid.html) rather than a full page.
HTMX swaps it into the DOM without a page reload.