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
| Decision | Rationale |
|---|---|
| Light background | Let project screenshots and SVGs stand out |
| Blue accent | Professional, trustworthy, tech-forward |
| Geometric fonts | Archivo + Space Grotesk pair cleanly |
| No dark mode | Avoids doubling CSS; light-only is simpler |
| Framer Motion | Subtle 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)
| Project | Category |
|---|---|
| AI SaaS Landing | 3D / Web |
| Council Multi-Agent System | AI |
| Project Vantage | Full-Stack |
| Executive Command Center | Dashboard |
| Lead Scout | Tool |
| OpenCode Dashboard v3 | Dashboard |
| TikTok Marketing Suite | Design |
| Voice Companion | AI / UX |
| HTML-to-PDF Renderer | Tool |
| n8n Automation Workflows | Automation |
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
- Light minimal design ages well. Dark/glassy trends come and go; white + accent is timeless.
- YAML content is a superpower. Non-developers can add projects without touching code.
- Static export is the right call for a portfolio. No DB, no server, no security headaches.
- SVG placeholders > broken images. A branded initial is better than a broken image icon.
- Separate deploy repo simplifies Cloudflare setup. Each project has its own build pipeline.