The Report: Why I Built My Own Static Site Generator
A designer's case for building custom tools instead of reaching for the nearest framework. How an Obsidian vault, a token grammar, and a compiler mindset replaced WordPress, Next.js, and the constant urge to redesign everything.
The Problem With Portfolio Sites
Every designer I know has rebuilt their portfolio at least three times. I have done it five. The pattern is always the same: you pick a tool, fight it into submission, launch something decent, then six months later you want to change one thing and the whole house of cards wobbles. WordPress needs plugins for everything. Squarespace looks like everyone else. Next.js is a rocket ship when all you needed was a bicycle.
The fifth time around, I stopped asking which tool should I use and started asking what does my portfolio actually need to do?
The answer was embarrassingly simple:
- Take my project write-ups and media
- Turn them into static HTML pages
- Deploy them to a server
- Never make me think about the plumbing again
No database. No server-side rendering. No hydration. No dependency updates breaking my site on a Tuesday morning. Just markdown in, HTML out.
The Vault: One Place for Everything
The entire site lives in an Obsidian vault. Every project, every art piece, every page on the site is a folder with a markdown file and an assets/ directory. The structure looks like this:
VAULT/
projects/
creatitive/
creatitive.md
assets/
bigboys/
bigboys.md
assets/
writing/
the-report/
the-report.md
art/
photography/
photography.md
assets/
That is it. No admin panel, no content API, no multi-step publishing workflow. I write in Obsidian the same way I take notes for everything else, and when I run npm run build, the system reads every markdown file with publish: true in the frontmatter and compiles it into a page.
The vault is the source of truth. If it is not in the vault, it does not exist on the site. If it is in the vault and marked publish, it ships.
This constraint is freeing. I never wonder where something lives, whether a draft got saved, or which version of a page is live. The vault is the canonical state of the entire website.
The Grammar: Tokens Instead of HTML
Markdown handles text well, but portfolios are image-heavy. Standard markdown image syntax () is fragile for a system where images live in organized subfolders and need to resolve across different build environments. So I built a token grammar instead.
Instead of writing file paths, you write short references. A hero image is just the filename and a #hero tag. An inline image is the filename and #img. An entire gallery is a folder glob and #imagestrip. The builder resolves these tokens at build time -- it finds the matching file in the page's assets folder regardless of whether it is a .jpg, .png, or .mp4. The imagestrip token expands a whole folder of numbered images into a scrollable carousel.
This is the core idea: authors write intent, the compiler resolves files. You never hardcode a path. You never worry about extensions. You say what you want and the grammar handles the rest. If a token cannot be resolved, the build fails immediately. No broken images in production, ever.
The grammar also handles metadata inline. Alt text and captions ride alongside the token on the same line with #alt and #cap tags. No separate config files, no CMS fields to fill out. Everything about a piece of media lives in one place.
Frozen Templates and the Compiler Mindset
Most site generators treat templates as living documents that get regenerated on every build. My system does the opposite. The HTML templates are frozen. They were designed once, by hand, in a code editor. The CSS classes, the DOM structure, the JavaScript hooks are all locked.
The builder does not generate templates. It injects content into them. HTML comment markers like CONTENT_MOUNT, NAVBAR_INCLUDE, and JSON_LD define where content goes. The builder reads the template, finds the markers, and replaces them with rendered HTML. The template itself never changes. This means:
- The design is stable. I can update content without risking the layout.
- The CSS is predictable. Class names do not shift between builds.
- The JavaScript works. DOM hooks are where the runtime expects them to be.
Think of it less like a CMS and more like a compiler. The grammar is the source language. The templates are the target architecture. The builder is the compiler that translates one into the other. If you have ever worked with a real compiler, you know the best thing about it is that it does exactly the same thing every time. No surprises.
Governance: The Report Comes First
The system has rules, and the rules are enforced. Not by convention, not by good intentions, but by pre-commit hooks and automated checks.
Every meaningful change requires:
- A changelog entry documenting what changed and why
- Updated canon documents if the grammar or contracts changed
- Passing contract tests that validate the grammar still works
The canon documents are the authority ladder of the project:
- Grammar defines how content is authored (token syntax, frontmatter fields, file structure)
- Architecture defines how the system is built (data flow, component responsibilities, build phases)
- Contract defines the non-negotiable rules (vault safety, template freezing, scope control)
- Invariants defines the locked decisions that are never revisited
If the code and the canon disagree, the canon wins. The code gets fixed.
This sounds heavy for a portfolio site. It is not. It takes about thirty seconds to write a changelog entry, and it saves hours of debugging because every decision has a paper trail. When I come back to the project after months away, I do not have to reverse-engineer my own choices. The report is already written.
Scaling Without Bloating
The grammar supports multiple layout types, each triggered by different tokens or frontmatter fields:
- Standard layouts for project case studies with heroes, inline images, and imagestrips
- Photodump layouts that render a masonry grid from a folder of images
- Specimen layouts for type specimens and grid-based art
- Blog layouts for long-form writing like this post
Each layout has its own frozen template, but they all share the same build pipeline. Adding a new layout means creating one HTML template and one render function. The grammar extends; the architecture does not change.
The blog layout, for example, adds article-specific features:
- Reading time calculated from word count
- Author byline in the footer
- Related project cards that link back to case studies
- BlogPosting structured data for search engines
- An RSS feed generated at build time
All of this comes from the same frontmatter and markdown I use for everything else. No separate blogging platform, no plugin ecosystem, no second system to maintain.
Why Not Just Use Next.js?
I get this question a lot. Here is the honest answer: because I do not need it.
Next.js is excellent software. So is Astro, Hugo, Eleventy, and a dozen other tools. But every one of them comes with opinions about how your site should work, a dependency tree that needs maintenance, and abstractions that sit between you and your output.
My system has:
- Zero runtime dependencies in the browser
- Three build dependencies: Node.js, marked for markdown parsing, and Sharp for image optimization
- Full control over every byte of HTML that ships
When something breaks, I know exactly where to look because I wrote every line. When I want a new feature, I add it to the grammar and the builder without waiting for a framework update or checking compatibility matrices.
The tradeoff is real: I maintain my own tooling. But for a portfolio site that changes a few times a year and needs to be rock-solid when a potential client visits, the calculus works. I would rather spend an afternoon writing a new render function than a week debugging why my framework's latest update broke image optimization.
The Point
This system is not the right choice for everyone. If you need a team of content editors, user authentication, or server-side logic, go use a real framework. But if you are a designer who wants a portfolio that:
- Loads fast because there is nothing to load
- Looks exactly how you designed it because the templates are frozen
- Never breaks because the compiler catches errors before they ship
- Stays organized because the vault enforces structure
Then maybe the answer is not picking the best tool. Maybe it is building the one you actually need.
The report comes first. The feature comes second. And the site just works.