Starting From a Reference Instead of a Blank Page
Most personal site rebuilds I've done in the past followed roughly the same pattern.
Open empty project. Have vague ideas about "the vibe". Spend three weeks slowly drifting into a site that somehow looks like every other developer portfolio on the internet despite promising myself this one would be different.
This rebuild started differently.
Before there was any real application code, I sat down with Claude and worked through the design system properly, end to end. Tokens, typography, spacing, layouts, component patterns, copy tone, all of it. Everything got dumped into a Claude Design/ folder and treated as read-only reference material.
That one decision ended up shaping the entire project.
The build phase stopped being "design while coding" and became "port an already-finished design into a real framework". Those are very different jobs. Separating them removed the mid-build redesign spiral I always fall into otherwise (ADHD and endless tweaking are close friends unfortunately).
Why Static Made More Sense
The site is tiny really.
Five pages total:
- Home
- Projects index
- Project detail
- Blog index
- Blog post
Everything else is just sections anchored off the homepage header.
For something that small, building a heavy runtime app felt faintly ridiculous. No server, no database, no API layer, no runtime fetching. The build process reads markdown from disk, renders HTML, and spits out a folder of static files. Deployment is basically just copying that folder somewhere.
Simple on purpose.
I ended up using Vite with vite-react-ssg for pre-rendering.
Vite because the dev loop is absurdly fast and mostly stays out of your way. vite-react-ssg because it quietly pre-renders routes into static HTML without dragging in the weight and complexity of something like Next.js.
For five pages, using Next felt pretty overkill.
The JavaScript budget is intentionally tiny too. Pages still work with JS disabled. The bundle only exists for the genuinely interactive bits, search, theme switching, command palette stuff, and even those run entirely client-side using data baked into the HTML during build.
I miss the older web a bit sometimes. Things should still mostly work when the lights go out.
The Stack, Briefly
A quick breakdown of the choices and the reasoning behind them.
TypeScript, Strict Mode
Mostly because frontmatter grows tentacles surprisingly quickly.
Posts and projects both have enough metadata fields that catching typos during build saves actual time later. Static typing feels slightly annoying right up until it saves you from yourself.
React 18
Hooks only, no classes, no state libraries.
For a site this size, useState and one small theme context is enough. Anything more would mostly be architecture cosplay.
Hand-Rolled CSS + Tokens
The whole design system runs through custom properties for colour, spacing, typography and motion.
I considered Tailwind briefly, but honestly it would have fought the token system more than helped it. Once the design tokens existed properly, regular CSS just felt cleaner and easier to justify.
Markdown + YAML Frontmatter
Content lives in markdown files with YAML frontmatter.
Parsed during build using gray-matter, then pushed through remark and rehype.
Code highlighting comes from shiki during build time, which means there's effectively zero syntax highlighter JavaScript shipped to the browser.
Again, static where possible.
Search
The search is literally substring matching over a small JSON index.
That's it.
The blog will probably only ever have tens of posts, not thousands. .includes() works perfectly well. Pulling in Lunr or Fuse would mostly be me pretending I had a scaling problem I don't actually have.
No Analytics
No trackers, no cookie banners, no analytics scripts, no chat widgets.
Partly privacy reasons, partly because I increasingly dislike how much of the modern web feels like being observed through one-way glass all the time.
The site has nothing to track, so it doesn't.
Working Against a Frozen Reference
One rule kept coming up throughout the build:
Port, don't redesign.
The reference components inside Claude Design/ already had the markup and class structure figured out. The job wasn't to "improve" them every five minutes, it was to move them into proper TypeScript modules, type the props properly, wire up the data and leave the design alone.
Sounds boring.
That's kind of the point.
Every time the temptation appeared to "just clean this bit up quickly", I tried to push back against it. If something genuinely needed changing, it changed in the reference first, then got ported into the real app afterwards.
Otherwise drift starts creeping in incredibly quickly.
I think this is one of the big failure points in a lot of AI-assisted projects right now. Models are extremely willing to "helpfully improve" things constantly. If there isn't a frozen reference somewhere, the project slowly mutates underneath you like a photocopy of a photocopy.
The fix is mostly discipline. Slightly dull discipline.
The AI Workflow
People keep asking how much of the site was "AI generated", which is a strangely difficult question to answer cleanly.
I didn't just type "build me a website" and watch magic happen.
The process happened in layers, and the role of the AI changed at each layer.
Design Pass
This was mostly long conversations.
Brand direction, tone, references, colours, layouts, sites I liked, sites I hated, weird emotional descriptors that barely make sense but somehow help steer design anyway.
Claude generated tokens, component structures and reference pages. I edited the bits that felt wrong. Then repeated that process over and over until it stopped feeling like generated work and started feeling like mine.
I knew that I wanted to use Google Material Design as the basis but I also wanted my own unique feel that you cant always get from a pre-made design system.
Architecture Pass
Fresh context window entirely.
The prompt was more or less:
Here is the design. Here are the constraints. Five pages, static output, markdown content, no trackers. Pick the stack and justify it.
Most of the recommendations held up surprisingly well after scrutiny. A couple got pushed back on, but the overall architecture survived basically intact.
Documentation Pass
This part turned out far more important than I expected.
Before any application code existed, the docs got written first.
CLAUDE.md at the root for hard project rules, then additional docs covering stack decisions, routes, design constraints and content structure.
Future Claude sessions can read those files instead of guessing. More importantly, future me can read them after disappearing for four days and immediately remember how the project is structured.
Honestly the docs help me almost more than the AI.
Build Pass
This is where most of the actual coding happened.
Port JSX. Type props. Wire content. Check browser. Repeat.
The interesting part is that the AI wasn't really inventing much at this stage. Most of the heavy thinking had already happened earlier through the design system and docs. The model became more translator than architect.
Which honestly feels like the healthier way to use these tools.
Content Pass
Mostly written by me.
Claude works better here as editor, sounding board and rubber duck than primary writer. If the voice stops sounding like me, the whole site starts feeling weirdly hollow very quickly.
The Quiet Importance of CLAUDE.md
The CLAUDE.md file at the repo root does a surprising amount of invisible work.
It tells every new session:
- there are only five pages
- the site is static
- the design reference is read-only
- runtime complexity is unwanted
- design changes flow from reference → app, never the other way around
Without that file, every new session starts guessing.
With it, sessions inherit context properly instead of behaving like a contractor who walked into the office halfway through the project and missed all the earlier meetings.
Things I'd Probably Do Differently
A few honest notes for future me.
The design phase took longer than expected because I kept trying to "perfect the prompt" instead of accepting that good design is iterative by nature. AI speeds up iterations, but it doesn't remove the need for them.
I also massively underestimated how useful the docs/ folder would become. Coming back to a project after a few days away normally involves a painful reload period where your brain slowly reconstructs context from fragments. Good documentation shortcuts that process massively.
And separating the frozen design reference from the live application has probably saved this project from itself more than once.
If there's one genuinely useful takeaway from this whole post, it's probably that.
Freeze the reference.
Why Write Any Of This Down?
Mostly because "how did you build the site?" kept coming up and writing a post once is easier than explaining it repeatedly in Discord messages.
But also because I think a lot of developers are quietly trying to work out what AI-assisted engineering actually looks like in practice right now.
Not the hype version. The real version.
The layered passes. The documentation. The frozen references. The constant context management. The slightly boring discipline that stops projects drifting off into nonsense.
The honest truth is that this still mostly feels like engineering.
The tools just got sharper.