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.pyreads that content and writes finished HTML into apublic/folder.- The server never runs Python. It only serves the files that were already built.
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.