Back to blog

TECH

Building a Portfolio from Scratch: Design Decisions & Architecture

22 May 20264 min read
PortfolioNext.jsDesign SystemArchitectureTailwind CSSFramer Motion

The Goal

Build a portfolio website that:

  • Showcases 10 projects with full case studies
  • Is beautiful but minimal — let the work speak
  • Needs zero servers — static export to Cloudflare Pages
  • Is easy to maintain — adding a project = adding one YAML file

Design System

Visual Identity

Concept:  Light + Minimal
Background:  #FFFFFF
Surface:     #F8F9FA
Accent:      #2563EB
Text:        #0A0A0B / #52525B
Headings:    Archivo (geometric sans-serif)
Body:        Space Grotesk (humanist sans-serif)

Key Decisions

DecisionRationale
Light backgroundLet project screenshots and SVGs stand out
Blue accentProfessional, trustworthy, tech-forward
Geometric fontsArchivo + Space Grotesk pair cleanly
No dark modeAvoids doubling CSS; light-only is simpler
Framer MotionSubtle scroll animations without bloat

Component Architecture

app/
├── layout.tsx          — Font loading, metadata, Header/Footer
├── page.tsx            — Hero → ProjectGrid → About → Skills → Contact
└── projects/[slug]/    — Dynamic case study pages

components/
├── shared/             — Header, Footer, Section, Tag
└── features/           — Hero, ProjectCard, ProjectGrid, AboutSection,
                          SkillsMatrix, ContactSection

lib/
├── projects.ts         — YAML loader
├── constants.ts        — Site config, nav, skills
└── utils.ts            — cn() helper

Content Strategy

Projects are stored as YAML files in content/projects/:

title: "Project Name"
subtitle: "Brief description"
category: "Web"
tags: ["React", "Three.js"]
featured: true
order: 1
challenge: "..."
approach: "..."
results: [...]

Adding a new project = creating one .yaml file. No code changes needed.

Static Export Strategy

The portfolio uses Next.js with output: "export" for Cloudflare Pages:

// next.config.ts
const nextConfig: NextConfig = {
  output: "export",
  images: {
    unoptimized: true,  // No Next.js image optimization needed
  },
};

No server, no database, no API routes (the contact form uses Web3Forms as an external service).

Screenshot Strategy

For projects without browser UIs, coloured SVG placeholders are used:

  • Generated by scripts/generate-placeholders.mjs
  • Each project gets a branded SVG with its name initial
  • Real screenshots added later when available

Deployment Workflow

Portfolio root (monorepo)
    → next build
    → Copy out/ to export repo
    → git push export repo
    → Cloudflare Pages auto-deploys

The portfolio lives in the monorepo but deploys from a separate git repo for Cloudflare Pages.

Results (10 Projects)

ProjectCategory
AI SaaS Landing3D / Web
Council Multi-Agent SystemAI
Project VantageFull-Stack
Executive Command CenterDashboard
Lead ScoutTool
OpenCode Dashboard v3Dashboard
TikTok Marketing SuiteDesign
Voice CompanionAI / UX
HTML-to-PDF RendererTool
n8n Automation WorkflowsAutomation

Key Results

  • 10 full case studies with challenge, approach, results, key code, and learnings
  • Static export — deploys to Cloudflare Pages with zero server costs
  • YAML content management — adding a project takes minutes
  • Responsive design — works on all screen sizes
  • SVG placeholders — no missing images, ever

Takeaways

  1. Light minimal design ages well. Dark/glassy trends come and go; white + accent is timeless.
  2. YAML content is a superpower. Non-developers can add projects without touching code.
  3. Static export is the right call for a portfolio. No DB, no server, no security headaches.
  4. SVG placeholders > broken images. A branded initial is better than a broken image icon.
  5. Separate deploy repo simplifies Cloudflare setup. Each project has its own build pipeline.