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.