A CTO who spent years building systems for other people finally built one for himself. It went about as well as you'd expect.
I review tech stacks for a living. I make architectural decisions that affect hundreds of engineers. I've debugged performance bottlenecks in systems handling billions of requests. And yet, building a simple personal website nearly humbled me.
Not "nearly." It did.
The first version had a broken logo. In production. For three days.
Building your own portfolio teaches you things a client project never will - mostly because there's no one else to blame.
Why I Didn't Just Use a Template
Everyone asks this. The honest answer isn't "I wanted full control" - it's that I started looking at templates and kept thinking "I'd just change this... and this... and this." At that point you're basically building from scratch anyway but with someone else's bad decisions baked in.
The trap is real: "It'll only take a weekend." Narrator voice: It did not take a weekend.
I decided on Astro - static site generation, zero unnecessary JavaScript, fast by default. In hindsight this seems obvious. At the time it felt like a gamble. Static site generation was having a renaissance but the ecosystem felt fractured. Would Astro stick around? Would the plugins I needed exist?
Spoiler: yes and yes. But I didn't know that when I committed to it.
The Architecture Decision I Got Right (and One I Got Wrong)
Right: keeping it static. No backend, no database, no auth layer. If there's nothing to hack, there's nothing to secure. The portfolio is a file you download. That's it. No midnight pages. No database connections timing out. No infrastructure to maintain.
Wrong: the CSS strategy. I started with a component-scoped approach, then switched to global CSS for performance, then tried a hybrid model to keep some scoping. By the time I settled on a pattern, I had three competing conventions fighting each other in the same codebase.
"I'll clean up the CSS later." Later never came. The mess shipped.
This is the single most costly decision pattern I've seen - not just in my code, but everywhere. The moment you think "I'll refactor this when I have time," you've built a technical debt factory.
The Gotchas Nobody Warns You About
This is where the story gets interesting. These are the six things that made me stop and stare at the screen wondering how they ever made it to production.
1. Lazy loading killed my logos above the fold
Added loading="lazy" to all images for performance. Sensible choice. Except the company logos in the hero section loaded blank on fast connections because the browser hadn't bothered fetching them yet. The human eye can't tell if a logo is slow to load or missing—it just sees a broken page.
Took embarrassingly long to diagnose. The fix was simple: don't lazy-load anything above the fold. Should have started there.
2. The OG image generator that silently skipped posts
Wrote a script to auto-generate Open Graph images for blog posts. It had a "skip if file exists" guard - totally reasonable. Except I forgot that it would also skip posts where the image was corrupted or half-generated. Deployed a post with a broken social preview. Found out when someone shared it.
The script succeeded. The image didn't exist. No error. No warning.
3. Hardcoded dates in the sitemap script
Built a sitemap generator. Hardcoded the last-modified dates at the top of the script as a lookup object. Every new post required a manual update or the sitemap would report stale dates. Discovered this three posts in when I noticed Google wasn't re-crawling updated pages.
That's not a system. That's a process that fails when you're busy.
4. Two sources of truth for blog metadata
One metadata file for the Astro build pipeline (TypeScript/ESM). A separate one for the RSS generator (plain JavaScript/CommonJS). Had to update both manually, in sync, every single time.
I built a process that required discipline to maintain. Discipline is not a system. Discipline is a bug waiting to happen. It happened.
5. RelatedPosts blowing up because one post was missing metadata
One post existed as a page file but had no metadata entry. The component that fetched related posts tried to look it up, found nothing, and threw an error. Not on that post's page - on every other post that had overlapping tags.
Took way too long to trace back. The error message was useless. The stack trace led nowhere.
6. The particle system on mobile
Built an animated canvas particle system for the hero section. Looked beautiful on desktop. On a mid-range Android phone it dropped to 8 FPS and made the whole page feel sluggish. Added a FPS cap and reduced particle count on mobile. Should have started there instead of desktop-first.
This one taught me: build for the slowest device first, not the fanciest one.
Performance Is a Feature Until It Becomes an Obsession
Set a target: sub-1-second load on 3G. Achieved it. Spent another two weeks shaving bytes. At some point the marginal gain per hour drops to zero. That point comes earlier than you think.
I optimized things that don't matter - hero animation render time. While ignoring things that do - font loading strategy. The Lighthouse score is not the product. The product is the product.
After you hit "good enough," the cost of improvement starts exceeding the value. A human won't notice the difference between a 0.8 second load and a 0.6 second load. But they'll notice if you take six weeks to ship.
Content Is Harder Than Code
Writing about yourself is uncomfortable. Writing about yourself in a way that doesn't sound like a LinkedIn brag post is harder.
I spent more time on the copy than on the code. This surprised me. The code was the easy part. Saying something true about myself that didn't sound performative—that was the hard part.
The first draft of my home page read like a job description. "Experienced leader with expertise in..." No. That's not how humans talk. I had to strip out all the corporate language and rewrite it as a person talking, not a profile performing.
Ended up writing a voice guide just to keep myself consistent. It's probably the most useful document I created during this whole process.
Deployment Is Where Assumptions Go to Die
Local builds pass. Production builds pass. And then you check the live site and something is slightly wrong in a way that's hard to articulate.
The deploy pipeline runs four scripts in sequence before the Astro build. If any of them silently produce bad output, the build succeeds and the output is wrong. The build logs don't tell you what happened. They just tell you it succeeded.
Case in point: the RSS feed was generating fine locally but producing malformed XML in CI because of a line-ending edge case. Validators said it was fine. Feed readers disagreed.
Always validate output, not just exit codes.
Exit codes are liars. They just tell you whether the process crashed. They don't tell you whether the process did what you wanted.
What I'd Do Differently
Start with content, not code
Know what you're saying before you build where to say it. I built the site first and wrote the content second. I had to restructure things because the content didn't fit the layout.
Document your conventions as you go, not after
Every time you make a decision that you think is obvious, write it down. It won't be obvious the next time you touch it.
Treat your own project with the same rigor you'd apply to a client's
The lack of a deadline is not a license to cut corners. It's an invitation to accumulate technical debt with no one to hold you accountable. That's how you end up shipping broken logos.
Accept that a personal site is never truly "done"
Ship it. Iterate. Move on. There's always something else you could optimize. The portfolio doesn't need to be perfect. It needs to reflect what you actually want to say. Perfection is the enemy of shipped.
The portfolio is live now. It works. It reflects what I want to say. The broken logo is fixed. Most of the gotchas are documented. The CSS is still a little messy.
If you're a developer thinking about building your own—do it. Not because it's easy, but because it'll show you exactly where your instincts are good and where they're embarrassingly optimistic.
The most honest thing a portfolio can show is the gap between what you know and what you thought you knew.