All writing
Python / Web / Static sites17 Oct 20265 min read

How this site is built

One Python script, no dependencies, and a folder of plain HTML at the end.

My previous portfolio was a Next.js app. For a site that is mostly text, links and a few lists, I wanted something smaller, so I rebuilt it as a static site generated by one Python script.

The idea

A static site is just files. The only question is how you make them without copy-pasting the header into every page.

  • The content lives in data.py (profile, work, projects, stack) and in markdown files (posts and case studies).
  • build.py reads that content and writes finished HTML into a public/ folder.
  • The server never runs Python. It only serves the files that were already built.
python
PROJECTS = [{"name": "FNewsteer", "dek": "Loss-prevention app"}]

def row(p):
    return f"<li><h2>{p['name']}</h2><p>{p['dek']}</p></li>"

html = "".join(row(p) for p in PROJECTS)

Data goes in, a function turns each item into HTML, and the result is written to a file. Every page on the site is that same loop with a different shape.

What the script does

  • Layout: one function holds the header, menu and footer, and every page's body is dropped into it. Changing a link is one edit and one rebuild.
  • Pages: one function per page type (home, projects, case study, writing, post, 404).
  • Markdown: a small parser handles headings, paragraphs, lists, quotes, code fences and links. That is all my posts need.
  • Syntax highlighting: a short regex tokenizer colours the code blocks at build time, so there is no highlighting library and no JavaScript for it.
  • Feeds: the RSS feed and the sitemap are generated on every build, so a new post shows up in both without extra work.

Why not a framework

  • There is nothing to install, so the build runs anywhere Python does.
  • The output is plain HTML and CSS, so pages load fast and there is little that can break.
  • The whole generator is a few hundred lines, so I can read it in one sitting and know what every line does.

The trade-off is real. A framework gives you components, routing and a large ecosystem. If I need interactive pieces inside posts, or a lot more content, I will move to something like Next.js with MDX. Until then, this does the job.

The design choices

  • One narrow column, dark by default, with a light theme that follows the system setting.
  • Small text (13px) with even smaller labels, so pages feel dense but stay readable.
  • Lucide icons for every link and arrow, one SVG file each, so they are easy to swap.
  • Motion is limited to a staggered load, a fade on scroll and a pulsing dot.

A bug worth remembering

After a rebuild, the new HTML shipped but browsers kept the old stylesheet, because the file name had not changed. The result was a page that looked half old and half new.

The fix was to add a short hash of the CSS and JavaScript to their URLs (style.css?v=bb10bb9d). When the files change, the URL changes, and the browser fetches the new version.

Shipping it

The site is a folder, so hosting is simple. I push the repo, point the host at the public/ directory, and it serves the files.

Pick the smallest tool that does the job, and move up when the job changes.