Skip to main content
Back to blog
case studynext.jsux designmongodbaccessibilityportfoliocmstailwind css

Building PortoHub: How I Designed and Engineered My Own Portfolio Platform

14 min read
Cover image for Building PortoHub: How I Designed and Engineered My Own Portfolio Platform

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

Full-stack web application code on screen
PortoHub was built from scratch as a production-grade portfolio platform with CMS capabilities.

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:

  1. Public visitor โ€” discovers work, assesses credibility, contacts or books a call
  2. 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 pages

The Block Editor: Designing for Editorial Flexibility

Design system and component library in Figma
Every design decision in PortoHub went through user research and usability testing.

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:

  1. Lean reads โ€” all public queries use .lean() for plain JS objects
  2. Compound indexes โ€” every list query is backed by a compound index (e.g., status + featured)
  3. Embedded SEO โ€” every content model embeds its own SEO metadata rather than joining a separate collection

Accessibility Engineering: Zero Violations at Launch

Analytics dashboard showing portfolio metrics
PortoHub includes built-in analytics to help designers understand how recruiters engage with their work.

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 custom normaliseHeadingLevels() function enforces heading order at render time for blog posts.
  • Icon-only buttons: All 40+ icon-only interactive elements have explicit aria-label attributes
  • Disclosure patterns: The mobile nav toggle uses aria-expanded + aria-controls pointing to the sidebar id
  • Form accessibility: All inputs have associated <label> elements via htmlFor/id, plus aria-required on 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:

  1. Design system audit earlier โ€” several accessibility fixes were in the second pass; they should be baked into initial components
  2. ISR over force-dynamic โ€” for blog posts and case studies, Incremental Static Regeneration would scale better at higher traffic
  3. 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.

Building PortoHub: Full-Stack Portfolio Platform Design & Engineering | Shahriar Shanto | Shahriar Shanto