How I Built My High-Performance Portfolio Website
A detailed walkthrough of the technologies and design choices I made while building my portfolio website.

Your portfolio should feel like you. Mine needed to reflect the way I think about system design: purposeful architecture, smooth interactions, and fast feedback loops. This post is a full breakdown of how I shipped the new version of my site using Next.js 16, React 19, Tailwind CSS 4, and a lean component system that keeps content flexible without sacrificing performance.
Project Goals
- Instant clarity – Highlight who I am, key services, and flagship projects above the fold.
- Editorial flexibility – Write case studies and blog posts in MDX without touching application logic.
- Performance-first – Hit Core Web Vitals targets on desktop and mobile, even with rich imagery.
- Maintainable DX – Keep folder structure, data models, and tooling predictable so future updates take minutes, not days.
Core Stack & Why It Works
| Layer | Choice | Reason |
| --------- | ------------------------------------------------------ | --------------------------------------------------------------------------------- |
| Framework | Next.js 16 (App Router) | Server Components for data-heavy sections, file-based routing for marketing pages |
| UI | React 19 + Shadcn UI + Tailwind 4 | Type-safe components with utility styling and accessible primitives |
| Motion | Framer Motion + Lucide + React Icons | Adds micro-interactions (see components/home/hero.tsx and projects.tsx) |
| Content | MDX (assets/blogs/*) + structured JSON in lib/*.ts | Keeps copy in plain text while components stay reusable |
| Tooling | pnpm, ESLint, Turbopack dev server | Fast installs, lint-on-save, hot reloads that mirror production |
Information Architecture
The App Router gives me composable layouts and route groups, keeping marketing pages isolated from case-study detail views. The trimmed structure below mirrors what ships in app/:
app/
├─ (root)/
│ ├─ page.tsx # Landing page assembling Hero, Services, Skills, Projects, Contact
│ ├─ blog/
│ │ ├─ page.tsx # Blog index fed by `lib/blogs.ts`
│ │ └─ [slug]/page.tsx# Dynamic MDX rendering per article
│ └─ projects/
│ └─ [slug]/page.tsx# Long-form case studies sourced from `lib/projects.ts`
└─ layout.tsx # Global metadata, fonts, analytics hooks
Each route layer stays server-first unless interactivity demands it. For example, components/home/projects.tsx uses "use client" so I can hydrate Framer Motion timelines and keep hover states buttery smooth, while static sections like app/(root)/page.tsx render server-side for instant Time to First Byte (TTFB).
Designing the Experience
Color + Typography
- Palette:
#cc641aprimary, warm beige background, and deep brown text (README.mdhas the full palette) to convey warmth and trust. - Typography: The global layout wires custom fonts through
app/layout.tsx, and Tailwind tokens keep headings and body copy consistent across sections.
Hero & Services Modules
HeroSectionanimates the profile image and call-to-action buttons with Framer Motion so the page feels alive within the first render.components/home/services.tsxpairs iconography from Lucide with concise service descriptions, keeping the grid responsive via Tailwind’s breakpoint utilities.
Projects Grid
- Data lives in
lib/projects.ts, so I can add a project by editing an object instead of touching JSX. - Each card shows category, year, tags, and quick actions (live demo, GitHub, detailed case study). The combination of
<Button>primitives from Shadcn UI andnext/image’s optimized loading keeps the grid sharp and accessible.
Skills & Workflow
- Skills are grouped by discipline and rendered as badges (
components/ui/badge.tsx), mirroring how I talk about my stack in interviews. - The workflow section outlines discovery → design → build → launch, and acts as social proof that there’s a repeatable process behind the visuals.
Content Pipeline with MDX + Structured Data
Blogs
- Draft an MDX file inside
assets/blogs/<slug>/index.mdx. - Export it through
assets/blogs/index.tsand register metadata insidelib/blogs.ts. - The blog index (
app/(root)/blog/page.tsx) maps overblogs, while[slug]/page.tsxrenders the MDX component directly. No extra MDX compiler setup is required thanks to Next.js’ native MDX support in the app directory.
Projects
lib/projects.tsholds everything from hero images to tech stacks and URLs, which means theapp/(root)/projects/[slug]/page.tsxfile can focus solely on layout.- Because the data layer is plain TypeScript, type errors catch mismatched fields long before deployment.
Performance, SEO, and Accessibility
- Images – Every showcase image funnels through
next/image, leveraging responsivesizesattributes to ship the smallest necessary assets. - Animations – Motion components only render on the client and use
viewport={{ once: true }}so scroll-triggered effects run a single time, reducing layout thrash. - SEO – Route files export
generateMetadatahelpers (seeapp/sitemap.tsandapp/robots.txt) ensuring canonical URLs, dynamic Open Graph tags, and crawler-friendly routes. - Analytics + Utilities –
components/common/google-analytics.tsxand helpers inlib/utils.tskeep telemetry and className concatenation reusable. - Accessibility – Navigation landmarks,
aria-labels on project links, and focus-visible outlines come from a mix of semantic HTML and Tailwind presets.
Tooling & Deployment
- pnpm keeps install times low and ensures lockfile determinism (
pnpm-lock.yaml). - ESLint + TypeScript (see
eslint.config.mjsandtsconfig.json) power editor feedback, catching unused imports or invalid props before a commit. - Turbopack dev server (
pnpm dev) mirrors production bundling, so hydration surprises are rare. - Vercel handles CI/CD; pushing to
mainauto-builds, runs linting, and deploys globally. Environment variables such asNEXT_PUBLIC_BASE_URLstay in.env.localduring local development.
Lessons Learned
- Content belongs close to content authors. Locating MDX files under
assets/blogs/keeps Git diffs surgical and empowers future collaborators. - Animations should guide, not distract. Framer Motion eased meaningful transitions (hero entrance, project hover) while keeping CPU usage low.
- Design tokens beat ad-hoc values. Leaning on Tailwind’s theme ensures consistent spacing and typography whether I’m coding a new section or updating the footer.
- Ship instrumentation early. Adding analytics + sitemap + robots files up front prevented SEO debt later.
What’s Next?
- Build an RSS feed so new articles syndicate automatically.
- Layer in light/dark themes via CSS variables and
next-themes. - Expand case studies with embedded metrics (load times, conversion lifts) sourced from actual project analytics.
Thanks for following along! If you have questions about any part of this build, reach out via the contact section or ping me on GitHub.