· 6 min

Building a Portfolio Site with Astro

How I rebuilt my portfolio from Framer to Astro, designed the system, and shipped it without a template.

The decision: why rebuild?

Framer is a tool. I wanted to own the system. Every color, every animation, every
interaction should be mine.

I believe in minimalism — not sparse design, but intentional design. Every pixel should
serve a purpose. Framer pushes you toward flashy. I wanted subtle.

Framer works for portfolios. But I wanted to add features: case study chat with AI,
dynamic components, API integrations. Can't do that in Framer. So I started from zero.

Control

Framer is a tool. I wanted to own the system. Every color, every animation, every
interaction should be mine.

Philosophy

I believe in minimalism — not sparse design, but intentional design. Every pixel should
serve a purpose. Framer pushes you toward flashy. I wanted subtle.

Scalability

Framer works for portfolios. But I wanted to add features: case study chat with AI,
dynamic components, API integrations. Can't do that in Framer. So I started from zero.

Part 1: The design system (Figma)

Before I wrote a line of code, I designed in Figma. Not to create pixel-perfect
mockups — to create constraints.

I locked in the typography: H1 at 32px weight 500, body at 15px weight 400 with
line-height 1.8, labels at 11px uppercase with letter-spacing. And a hard rule — font
weight never above 500. No 600, no 700. Just 400 and 500.

Why? Constraints force clarity. Without a bold weight, you can't rely on boldness. You use
size, color, spacing instead. The hierarchy becomes more intentional.

The color palette is eight values: background, text primary, text secondary, text
tertiary, border — in both light and dark variants. No gradients, no shadows, no
decorative accent colors. Every color exists for a semantic reason. The green accent
appears only for interactive states and badges. It's earned.

For spacing, every value is a multiple of 8: 8, 12, 16, 24, 32, 48, 64. No arbitrary
numbers. This creates visual rhythm without thinking.

Max content width: 680px centered. At this width, a line of body text has roughly 80
characters — optimal for reading. Wider, and your eye travels too far. Everything lives
in a container with this max-width. Mobile scales down. Desktop respects the constraint.

The system stayed intentionally small. Enough range to create hierarchy and rhythm, not enough to hide weak decisions behind decoration.

Part 2: The architecture (Astro)

I chose Astro for one reason: it gives me static generation with the option to add
dynamic features. Generate static HTML (fast, cheap to host), add interactive components
where needed, and send no JavaScript to the client unless you ask for it. This aligned
with my philosophy: ship only what's necessary.

Key structural decision: one layout file, one global CSS file. No scoped styles,
no CSS-in-JS. Just plain CSS with semantic class names. With one CSS file, you're forced
to think systematically. No buried styles, no duplicate logic. Everything is visible.

I built components for reusable patterns, not pages. Nav, Footer, ProjectCard, Badge —
each single-purpose. The CaseStudy component is the key one: instead of building five
separate case study pages with repeated structure, one component takes data and renders
consistently. Around 60 lines per page instead of 400. This is the power of reusable
components.

The stack was small on purpose. Each layer had one job, and anything that didn't justify itself got removed.

Static-first output, with dynamic pieces only where interaction actually mattered.

One global file, CSS variables everywhere, no utility churn and no hidden structure.

Nav, Footer, Badge, ProjectCard, and CaseStudy built once and reused deliberately.

Cheap static hosting for most pages, serverless only where the AI chat needed it.

Part 3: CSS as a system

I could've used Tailwind. I chose not to. Tailwind is a utility framework — fast to write
but it creates invisible structure. The CSS is hidden inside compiled output. You can't see
it, reason about it, or tweak it.

I wanted the opposite: visible structure. Plain CSS with custom properties. Every color
comes from a variable. Change the variable and it cascades everywhere. For dark mode,
instead of maintaining two CSS files, I just flip the variables with a single
setAttribute call. No flash of white on load, no CSS bloat.

The hard rules I set: no shadows, no gradients, no decorative effects, max two font
weights, CSS variables everywhere. These aren't limitations — they're forcing functions.
They made me think harder about every design decision. The result is more cohesive than
if I had infinite options.

Part 4: Case studies as the centrepiece

Case studies follow a strict section order: Hero, Problem, Research, Strategy, Solution,
Collaboration, Results, Reflection. Every case study uses this sequence. The consistency
is intentional — it trains readers. They know what to expect, so they focus on the
content, not the structure.

For layout, I use a two-column grid inside each section: a 120px label column on the left
and a fluid content column on the right. This creates visual rhythm. The labels become a
scanning guide. Readers can skim by reading labels, or dive deep into content.

Metric cards are prominent — 32px numbers, green for positive deltas. Results are the
payoff. They deserve space. I don't bury them in a paragraph. They're the first thing
you see when you reach that section.

Every case study follows the same sequence and layout logic instead of reinventing the storytelling each time.

Consistency lets readers spend their attention on the substance of the work, not on learning a new page pattern every time.

The site feels authored rather than assembled. Reuse improves maintainability and also improves reading rhythm.

Key numbers live in dedicated components and section patterns instead of being buried inside body copy.

Results are the argument. If they are visually secondary, the whole case study loses sharpness.

The pages communicate impact fast, even when someone only scans for a few seconds.

Lessons learned

Limiting myself to 8 colors, one font, and no shadows forced better decisions and produced a more coherent result.

The CaseStudy component alone saved over 1,200 lines of duplicate code. Reuse is not just tidy, it compounds.

It is easy to decorate a page. It is much harder to make it persuasive and calm without visual crutches.

They make the design system explicit. Every token stays visible, auditable, and adaptable without adding abstraction debt.

What I'd do differently

Build components earlier, not after. Starting with reusable components would've saved time
and enforced the system from the start.

Add analytics from day one. I don't know what people click on or where they drop off.
That data would've informed design decisions I was making blind.

Use TypeScript everywhere. I coded defensively, but types would've caught bugs in data
flow before runtime.

Looking for a designer who owns the problem, not just the deliverable.

Available for full-time roles in product-led teams where experimentation drives decisions.