Three days ago, this site was a Dynadot parking page. Today it's a full personal site — six pages, a content system, a working contact form backed by a real .NET API, deployed across Cloudflare Pages and a $5 VPS.

I built most of it pairing with Claude.

This isn't another "AI wrote my code!" post. It's more honest than that — a build log of what AI actually helped with, what it got wrong, and the decisions I still had to make myself.

Why bother with a personal site

I've been freelancing on and off for a few years. Most of my leads came through referrals and a pinned GitHub repo. That worked, but it put a ceiling on who could find me.

A personal site does three things a GitHub profile can't:

  1. It tells a story, not just a resume
  2. It ranks for long-tail searches ("nuxt dotnet freelance developer")
  3. It's where cold leads can actually reach you

The goal wasn't perfection. It was something real, shipped, that represents me.

The stack, and why

I picked the stack before talking to the AI. This matters. If you let the AI pick, you get whatever is trendy in its training data.

  • Frontend: Nuxt 4 + Nuxt UI 4 + Tailwind v4 + TypeScript
  • Content: @nuxt/content v3 (Markdown-driven blog, projects, travel)
  • Backend: .NET 8 Minimal API (just for the contact form)
  • Hosting: Cloudflare Pages (frontend) + $5 VPS (backend)
  • Images: Cloudflare R2 with a custom domain

Three constraints drove these choices:

It should stay cheap. Total cost is around $5/month. Cloudflare Pages and R2 are effectively free at my scale.

It should show what I can actually do. Having a real .NET backend on a real VPS — even just for a contact form — is a live demo of my full-stack work. A static-only site can't claim that.

It should be fun to maintain. All content lives in Markdown files inside content/. Writing a blog post is just opening VSCode and typing. No CMS. No database. No admin panel.

Three days, roughly

Day 1 — Scaffolding and structure. Six pages wired up with placeholder content. Design tokens locked in: Playfair Display serif, teal palette, lots of whitespace. The shape of the site was visible by evening.

Day 2 — Content system and backend. Migrated mock data into @nuxt/content across three collections (blogs, projects, travel). Built the .NET 8 contact API with SMTP email delivery and rate limiting. Wired the form to the backend.

Day 3 — Polish and ship. Error page, SEO meta tags, mobile breakpoints, image pipeline to R2, DNS, SSL, deployment. All the finish-tax work that turns a prototype into a live site.

No weekend work magic. Just focused days with a collaborator that never slept.

Where AI actually helped

Let me be specific, because "AI helped me build this" usually means nothing.

Scaffolding that would have taken a full day took two hours

I described the six pages I wanted with rough layouts. Claude generated the initial Vue files with proper structure, placeholder content, and consistent styling tokens. I reviewed, caught inconsistencies, and iterated.

This is the sweet spot for AI right now — bulk generation of familiar patterns. It knows what a blog list page looks like. It knows how Nuxt pages are structured. It can write fifteen similar-but-not-identical components in ten minutes.

Doing this by hand would have been a full day of copy-paste-modify. I'd have been bored. I'd have introduced bugs.

Migration from mock data to @nuxt/content was painless

I started Day 1 with hardcoded arrays of blog posts in a composable. Day 2 morning, I wanted Markdown files instead. I described the migration in two sentences. Claude rewrote the pages to use queryCollection(), added the right schema to content.config.ts, and walked me through the directory structure.

The migration across three collections took about thirty minutes. The nested travel structure (content/travel/japan/xxx.md) needed a judgment call — I wanted /travel/[slug] routing instead of /travel/[country]/[slug], which forced a different query pattern. I made that call. Claude implemented it.

Debugging the weird stuff

On Day 3 I hit a Vue 3 hydration mismatch warning that seemed unrelated to anything I'd just changed. I pasted the stack trace. Claude diagnosed it as an SSR issue with my scroll-reveal composable — CSS was applying opacity: 0 during hydration, before the IntersectionObserver could kick in, creating a mismatch between the server HTML and the hydrated state.

The fix was subtle: move the "hidden state" from CSS to a JS-applied class, so the server HTML matched what the client first renders. I wouldn't have figured that out alone in under an hour. Claude found it in one exchange.

Where AI didn't help

Equally specific, because this matters more.

Taste

Claude can generate a hero section. It cannot tell me whether a serif font for headings is right for this site.

That decision — Playfair Display serif, teal palette, lots of whitespace — was mine. The AI can execute a design. It cannot make the design feel like me.

Every choice that touched identity was mine: the color palette, the typography, the copywriting voice, the "Lost at sea" tone on the 404 page. When I let Claude write copy, it came back as generic marketing-speak. When I wrote the copy and asked Claude to polish tone, it was better.

AI is a great executor, a poor art director.

Deciding what not to build

In three days, the discipline to cut matters more than the speed to build. I considered adding:

  • A newsletter signup
  • Comments on blog posts
  • An analytics dashboard
  • A WebGL-animated hero section
  • i18n for English + Chinese

I built none of them. Not one.

Each would have added a day of work and zero client acquisition. Claude would have happily helped me build all five. The discipline to cut features is a human skill. AI defaults to "yes, let's build that" because it has no stake in my time.

Product positioning

The hardest part of the site isn't the code. It's the Services page — deciding what I offer, what I don't, what pricing tier I show, what process I describe.

I spent more time on that one page than on any other. Claude helped me draft and iterate, but the actual choices — "I don't do WordPress sites", "I'm fine with three-week projects but not three-month ones", "my minimum is $1,000" — those were me reading my own calendar and bank account.

The parts nobody talks about

Two surprises worth flagging if you're about to do something similar:

The "finish" tax

Day 3 took longer than Day 1 and Day 2 combined. Error pages, SEO meta tags, mobile breakpoints, the contact form rate limiter, the hydration bug, the R2 image upload pipeline, the deployment guide, the DNS configuration, the SSL renewal cron.

None of this is glamorous. All of it is required.

If you've built a side project and felt like you were "almost done" for the last stretch — yeah, you were. The finish tax is real, and it doesn't care how fast the scaffolding went.

The deployment rabbit hole

I initially tried Cloudflare Workers because it sounded fancier. Spent a morning on it before I realized it was the wrong tool — I didn't need edge compute, I needed static hosting. Switched to Cloudflare Pages and was deployed in ten minutes.

Sometimes the answer is "use the simpler thing." AI tends to suggest more powerful tools than you need. Push back.

What it cost

ItemMonthly
Domain (xtop.dev)~$0.60 (amortized)
VPS ($5/month, for .NET API)$5.00
Cloudflare Pages$0 (free tier, unlimited bandwidth)
Cloudflare R2 (image storage)$0 (under 10GB)
SMTP (QQ Mail for contact form)$0
Total~$5.60/month

First client inquiry pays for 10+ years of hosting.

Would I do it again this way?

Mostly, yes.

Kept: Nuxt + @nuxt/content + Cloudflare Pages. Markdown-driven sites are a joy to maintain. Every new post is one file, one git push.

Kept with caveats: The .NET backend. Overkill for just a contact form, but it's a live portfolio piece — "this developer can actually deploy a Linux VPS with Nginx and systemd" — that a Vercel serverless function can't replicate.

Would change: I'd pick the typography and color palette in the first hour instead of drifting into it. Deciding design late caused rework. On a three-day timeline, two hours of rework is a lot.

Surprised me: How much AI helped with the boring parts (scaffolding, refactors, deployment scripts) versus the interesting parts (design, positioning, voice). The boring parts are where AI shines. The interesting parts stay human.

Three days is possible because AI compresses the boring parts. It doesn't compress the interesting ones.

If you're thinking of doing this

My suggestions, in order of importance:

  1. Pick your stack before you open the AI chat. Otherwise you'll build a trendy stack instead of one you'll enjoy maintaining.
  2. Ship one version end-to-end before you polish. My first version was ugly. Shipping it unlocked every subsequent decision.
  3. Write your own copy. Let AI edit it.
  4. Cut features ruthlessly. A focused site out-performs a bloated one.
  5. Get to the contact form fast. Everything upstream of the contact form is scaffolding for the contact form.

The site took three days. It works. People can reach me. I can write new posts in Markdown and git push.

That's the whole pitch.


Thanks for reading. If this resonated and you need a developer who pairs well with AI but still makes the hard calls himself, my inbox is open.