Building This Site

The Backstory
I've been writing backend code professionally for years. Servers, databases, APIs — the comfortable stuff. Data in, data out, nobody judges you for how your JSON response looks.
Frontend was a blind spot I was perfectly happy to keep blind. I knew of CSS — selectors, the cascade, display: flex — but I couldn't write a layout from scratch without a StackOverflow tab open. JavaScript wasn't just unfamiliar; it was genuinely unappealing. The tooling churn, the type erasure at runtime, the "it works on my machine" of browser inconsistencies. TypeScript papered over the cracks, but it still felt like I was fighting uphill.
What changed? I wanted a personal website. Not a template. Not a Squarespace. Something I built, that reflected how I think about software. And I wanted to prove to myself that I could do the full stack, not just the backend half.
Why Leptos
I evaluated the options. React (no, still JavaScript). Svelte (interesting, but new syntax). SolidJS (closer, but still JS ecosystem). Then I found Leptos.
Leptos is a fullstack Rust framework. You write UI components in Rust, they compile to WASM for the browser, and the server side just works — same language, same types, same toolchain. Fine-grained reactivity means no virtual DOM overhead. The router, the server functions, the hydration — all in one framework.
For me, the killer feature wasn't performance. It was continuity. I could write my database queries, my API handlers, and my HTML templates in the same language. No context switching. No "now I have to remember TypeScript patterns." Just Rust.
The Thaw Detour
I started with Thaw, a Leptos component library. Beautiful components — tables, buttons, inputs, dialogs — all pre-built and ready to use. It felt like a cheat code.
But the cheat code had a cost. Thaw comes with opinions. Design opinions. Layout opinions. Naming conventions. Pretty soon I wasn't writing my site — I was writing Thaw's site. Want to change how a button looks? Gotta fight the framework. Want a layout that doesn't match the library's assumptions? Hope you enjoy override CSS.
I realized I was spending more time fighting the component library than I would have spent writing the HTML myself. So I ripped it out and replaced everything with raw Leptos HTML elements.
// Before: Thaw
<Button variant="primary" on_click=move || ...>"Click me"</Button>
// After: Raw HTML
<button on:click=move || ... class="px-4 py-2 rounded-lg bg-brand text-white hover:bg-brand-dark">
"Click me"
</button>That one decision taught me something important: component libraries speed you up on day one, but they cost you every day after. For a personal project where you own the full surface area, writing raw HTML gives you total control and zero surprises.
The Raw CSS Reality Check
With Thaw gone, I needed to style everything myself. I went full raw CSS. No frameworks, no preprocessors. Just style/main.css and a lot of classes.
It went okay at first. But as the site grew — about page, projects, blog layout, dark mode — the stylesheet grew with it. I ran into the three classic CSS problems:
- Specificity battles. One rule works until another rule with a higher specificity overrides it. The cascade stops being your friend at about 500 lines.
- Naming things.
.project-card-header-wrapperis a real class I wrote. It's not my finest work. - Duplication. The same 6-7 property combos kept appearing. I started copy-pasting entire rule blocks between components.
I wasn't writing bad CSS. I was writing predictable CSS for a project that had outgrown a single stylesheet. The problem wasn't me — it was the approach.
Tailwind: The Epiphany
I'd dismissed Tailwind for years. "It's just inline styles with extra steps." "It makes HTML ugly." I was wrong.
Tailwind's utility-first approach isn't about writing less CSS. It's about constraint-based composition. You're not styling an element — you're building it from atomic visual primitives. Each utility class does one thing well, and you combine them like building blocks.
What surprised me most wasn't the styling speed. It was the architectural feedback loop.
When you're repeating the same Tailwind classes — say, flex items-center gap-2 p-4 rounded-lg appears in four places — it becomes visually obvious that you need a component. The repetition stares you in the face. With raw CSS, that same repetition was hidden behind class names like .card-base and .section-inner, obscuring the architectural debt.
// Tailwind makes abstractions obvious
// When you see this repeated:
<div class="flex items-center gap-2 p-4 rounded-lg bg-surface-2">
// You extract it:
<Card>Tailwind didn't just improve my styling workflow — it improved my component architecture. The visual noise of repeated classes forced me to build better abstractions.
What I Learned
- Component libraries are a tax, not an investment. For a personal project, raw HTML gives you full control and zero framework coupling. You can always add libraries later when you know exactly what you need.
- Utility-first CSS exposes architecture problems. If Tailwind feels repetitive, your components aren't granular enough. Listen to what the repetition is telling you.
- Rust on the frontend is production-ready. Leptos compiles to WASM that loads fast, runs fast, and doesn't pull in a 200KB JS runtime. The developer experience — hot reloading, type safety, shared types with the backend — is genuinely good.
- Learning fullstack means learning fundamentals. I could have used a template. I could have used Squarespace. But by building from scratch, I learned how CSS actually works, how hydration differs from SSR, and how to deploy a WASM app to production. Those skills transfer everywhere.
Wrapping Up
This isn't a "Rust is the best" post. It's a "I learned fullstack fundamentals by building something real in a language I love" post. Leptos isn't the only good framework. But it was the right one for me, because it removed the biggest barrier: writing JavaScript.
If you're a backend engineer who's been putting off learning frontend — pick a framework in a language you already know. The fundamentals (layout, state management, routing, deployment) are the same regardless of the syntax. And you might surprise yourself with what you can build.
The site is open source. You can read the full codebase on GitHub.
What's Next
This post covered the what and why of building the site. The next post is about the refactor that followed: how I split the project into a multi-crate Cargo workspace to run two independent Leptos apps, and the CI pipeline that deploys them conditionally.