2026 · Author · designer · documentation
Neofolio
An opinionated, AI-readable portfolio template for developers in the AI age. MIT-licensed. The template this site runs on.
- stack
- Hugo, Tailwind, HTMX, Alpine.js, TypeScript
- tags
- template, open-source, hugo, ai-readable
- status
- shipped
Problem
Most developer portfolio templates optimize for one audience: humans who land on the site. None of them optimize for the AI systems that — increasingly — summarize the developer to those humans before they ever click through. A template that ships in 2026 has to do both.
Constraints
A handful of non-negotiables shaped every decision:
- Static HTML on first paint. No SSR, no JS above the fold. Every meaningful sentence has to live in the source HTML for AI scrapers to index without rendering JavaScript.
- One JSON-LD entity graph, not many. Per-page schemas (BlogPosting, SoftwareApplication, Person.hasOccupation on /cv) reference one canonical
PersonandWebSitevia stable@ids, so knowledge graphs merge them into one record instead of treating each page as a separate entity. - Lighthouse 90+/100/100/100. Self-hosted Inter font, HTMX + Alpine islands that hydrate on interaction only, Tailwind emitted by the standalone binary so only the classes actually used ship.
- Forkable in minutes. Identity in one file (
data/site.yaml). Theme in a handful of CSS variables. Content in plain Markdown, with YAML data files for the structured stuff. - Node-free by construction.
make setupfetches the Hugo and Tailwind standalone binaries — nonpm install, nonode_modules, no framework runtime shipped to the browser. TypeScript compiles through Hugo’s built-in esbuild.
What I did
Built it on Hugo, with HTMX driving SPA-style navigation, a small vanilla-TypeScript bundle handling the nav, scroll-spy, and project tag filter, and Alpine reserved for the one piece that genuinely needs reactive client state — the network explorer. Everything else is static HTML rendered at build time.
The JSON-LD layer is the load-bearing piece. Shared schema partials expose a stable personId/siteId so every page references the same entities. Person.hasOccupation is generated from a single data/cv.yaml source of truth, so the homepage Experience block, the /cv page, and the structured data all stay coherent. /llms.txt and the JSON Feed are both emitted at build time — /llms.txt per the llmstxt.org spec.
It ships with a Cloudflare Pages deploy workflow, and because the base path is driven entirely by baseURL, the same hugo --minify output serves a sub-path host like GitHub Pages just as well. The network page composes intro requests via a prefilled mailto: link, so there’s no backend to run.
It started life as an Astro + Vue build; I later rebuilt it on the Node-free Hugo stack. Carrying the same AI-readability contract across two completely different rendering models forced me to articulate decisions I’d otherwise have left implicit — the kind of documentation discipline you write for the next forker that turns out to be good for you too.
Outcome
MIT-licensed at github.com/KoldenAxelson/neofolio. Live demo at koldenaxelson.github.io/neofolio. The site you’re reading right now is a Neofolio fork — the cleanest possible proof of concept.
What I’d do differently
The AI-readability angle was the bet I wasn’t sure would pay off when I started. It has — and if I were rebuilding today I’d push further, with more aggressive Person.knowsAbout enrichment and explicit mentions graphs linking articles to projects to people. The architecture supports it; I just didn’t take it that far in v1.