I'm a UX/UI designer by profession and a web developer by necessity. For the past year I've been building PortoHub — my personal portfolio platform — from a blank Next.js app to a production system with a full CMS, appointment booking, nine switchable themes, and a WCAG 2.1 AA-compliant front end.
This post is both a case study and a reflection. I'll walk through every significant design and engineering decision — from information architecture to database schema design to accessibility engineering — so you can build something similar, or just understand how a real portfolio system comes together.
💡 What you'll learn
How to design a block-based CMS from scratch. Why MongoDB is the right choice for editorial content. How to build a theme-switching system with zero data migration. How to audit and fix WCAG 2.1 AA violations systematically.
Why I Built My Own Portfolio System
The honest answer: I couldn't find a platform that matched the depth of the work I was presenting. Here's what every option was missing:
- Squarespace / Webflow — beautiful but rigid. No custom block types. Monthly fees. You don't own the data.
- Static site generators (Hugo, Gatsby) — full control but no visual CMS. Publishing requires a terminal session.
- Headless CMS + Next.js — good architecture, but adds a third-party dependency and monthly cost for Contentful or Sanity.
PortoHub's answer: own everything. One codebase. One database. No monthly CMS fee. Full editorial control via a custom block editor.
Architecture: Two User Journeys, One Codebase
The first structural decision was separating two distinct user journeys:
- Public visitor — discovers work, assesses credibility, contacts or books a call
- Admin (me) — creates content, manages appointments, configures the site
Next.js App Router handles this naturally with route groups: (public) for visitor pages and a protected /admin tree for the CMS. Authentication uses next-auth v5 with JWT sessions and bcrypt password hashing.
src/app/
├── (public)/ # All visitor-facing routes
│ ├── page.tsx # Home — theme-driven
│ ├── blog/ # Blog listing + [slug] posts
│ ├── projects/ # Project listing + [slug] detail
│ ├── case-studies/ # Case study listing
│ ├── about/ # Skills, experience, education
│ ├── contact/ # Contact form + social links
│ └── book/ # Appointment booking
└── admin/ # Protected CMS (JWT session)
├── blogs/ # Blog post management
├── projects/ # Project management
├── settings/ # Global site configuration
├── appointments/ # Booking management
└── …30+ more pagesThe Block Editor: Designing for Editorial Flexibility
The block editor is the core product decision. The requirement: every editorial action should feel like Notion, not like filling a database form.
It supports 13+ block types grouped into three categories:
- Basic: Text (Tiptap rich text), Heading (H2–H4), Image, Video embed, Divider
- Advanced: Code snippet, Quote, Callout (info/warning/success/error), Metrics display
- UX-specific: Problem statement, Research findings, Process steps, Solution description, Accessibility notes
Blocks are drag-to-reorder via dnd-kit. Each block is a plain object with a type enum and a data: Mixed field — meaning a metrics block and a quote block can have completely different shapes without schema conflicts.
Nine Themes, One Dataset
The theme system is built on one insight: themes are presentation layers, not data structures. All nine themes receive the same ThemeData interface — settings, projects, blogs, skills, achievements, services, testimonials, gallery. Switching themes in the admin writes a single settings field. No migration. No rebuilds. The active theme is read at request time and rendered server-side.
Available themes: Default (Minimal Pro), Dark, Creative, Minimal, Glass (glassmorphism), Urbanist, Bento (grid layout), and Sidebar.
The best way to learn full-stack design is to design a product you'll actually use — and then build it.
— Shahriar Alam Shanto
Database: Why MongoDB for Editorial Content
Relational databases are a natural choice for structured data, but editorial content blocks have heterogeneous shapes. A metrics block stores an array of label/value/unit objects. A quote block stores text, author, and source URL. Storing these in a relational schema means null-padded columns or a serialised JSON blob — both worse than MongoDB's native document model.
The 24 Mongoose models follow three patterns:
- Lean reads — all public queries use
.lean()for plain JS objects - Compound indexes — every list query is backed by a compound index (e.g.,
status + featured) - Embedded SEO — every content model embeds its own SEO metadata rather than joining a separate collection
Accessibility Engineering: Zero Violations at Launch
Ship something real. The gap between a design exercise and a live product teaches you more than any course.
— Product Design Principle
Accessibility was a first-class concern, not a post-launch audit. The systematic approach:
- Semantic structure: Single
<h1>per page. A customnormaliseHeadingLevels()function enforces heading order at render time for blog posts. - Icon-only buttons: All 40+ icon-only interactive elements have explicit
aria-labelattributes - Disclosure patterns: The mobile nav toggle uses
aria-expanded+aria-controlspointing to the sidebarid - Form accessibility: All inputs have associated
<label>elements viahtmlFor/id, plusaria-requiredon required fields
Result: zero critical axe-core violations on public pages at launch.
- 30+
- Admin pages
- 47+
- API endpoints
- 9
- Themes
- 0critical
- Accessibility violations
What I'd Do Differently
Three things I'd change on a rebuild:
- Design system audit earlier — several accessibility fixes were in the second pass; they should be baked into initial components
- ISR over force-dynamic — for blog posts and case studies, Incremental Static Regeneration would scale better at higher traffic
- Alt text required at upload time — image accessibility currently relies on discipline, not validation
🎓 The takeaway
Your portfolio is always-on evidence of how you work. Build it like a client product — with research, information architecture, and a tested accessibility baseline. If it's good enough to showcase, it's good enough to build properly.