February 22, 2026 · My Learnings · 5 min read

How I Built This Portfolio: Architecture and Tech Stack

A walkthrough of the architecture, tech stack, and key decisions behind this portfolio — Next.js, Prisma, Tailwind, and AI-powered features.

  • Next.js
  • Architecture
  • TypeScript
  • Prisma
  • Tailwind CSS
Share

This post is a walkthrough of how I built this portfolio website, the tech stack, architecture decisions, and key patterns that hold it all together. The full source code is available on GitHub.

Tech Stack

  • Next.js 16 (App Router) Full-stack framework with Server Components, Server Actions, and ISR.
  • TypeScript + React 19 End-to-end type safety.
  • PostgreSQL (Neon) Serverless Postgres with connection pooling via PgBouncer.
  • Prisma 7 Type-safe ORM with migration tooling.
  • Tailwind CSS 4 + shadcn/ui Utility-first styling with accessible component primitives.
  • NextAuth.js v5 JWT-based auth with a Credentials provider for admin access.
  • Cloudflare R2 S3-compatible file storage with zero egress fees.
  • Vercel AI SDK + Claude AI-powered resume parsing and document-to-blog conversion.
  • PostHog Privacy-friendly analytics with custom event tracking.

Architecture

The app is organized in clear layers:

Client (Server + Client Components)
  ↓
Middleware (NextAuth edge auth)
  ↓
Server Actions ("use server" mutations)
  ↓
API Routes (file uploads, AI parsing, auth)
  ↓
Prisma (type-safe queries, transactions)
  ↓
PostgreSQL (Neon, pooled via PgBouncer)

All content (blog posts, projects, experience, profile) lives in the database. No MDX files, no headless CMS. This means I can edit everything through the admin dashboard without redeploying.

Key Decisions

Server Actions for All Mutations

Every create, update, and delete operation is a "use server" function. No REST API boilerplate, just call a function from the UI and it runs on the server. Each action follows the same pattern: authenticate, validate with Zod, query the database, revalidate cached paths.

Singleton Pattern

The Profile and SiteSettings models use a fixed ID of "main", always exactly one row. Simple to query, impossible to accidentally duplicate.

Edge-Safe Auth Split

NextAuth v5 config is split in two: a lightweight edge-safe config for the middleware (no bcrypt, no Prisma), and a full config for the actual credential verification. This lets route protection run on the edge while auth logic stays in Node.js.

ISR for Performance

Public pages use Incremental Static Regeneration. The home page revalidates every 60 seconds, blog posts every 5 minutes. Effectively static for visitors, but content changes reflect within minutes.

Blog System

The blog is the most feature-rich section. Posts are written in a Tiptap rich text editor with syntax-highlighted code blocks. Content is stored as both HTML (for rendering) and JSON (for editor state restoration).

Comments are anonymous with IP-based rate limiting (5 per 10 minutes), honeypot spam protection, and admin moderation. There's also a Document-to-Blog Importer that uses Claude to parse uploaded documents into structured post fields.

Design System

The visual identity uses a constrained palette: Paper (#F7F7F5) backgrounds, Forest (#1A3C2B) text, Coral (#FF8C69) accents, with a flat 2px border radius throughout. Typography pairs Space Grotesk (headings), Inter (body), and JetBrains Mono (code). Dark mode swaps the entire palette via CSS custom properties, persisted to localStorage with a system-preference fallback.

Infrastructure

Files are stored in Cloudflare R2 (chosen for zero egress fees), uploaded via an admin API route with type/size validation. Analytics run through PostHog with a reverse proxy at /ingest to bypass ad blockers. The site deploys on Vercel with prisma generate running before next build.

What I'd Improve

  • Content versioning no revision history on blog posts yet.
  • Image optimization uploaded images are served as-is from R2.
  • Full-text search not implemented, Postgres tsvector would be natural.
  • Automated tests no Playwright or Vitest coverage yet.

Every choice was driven by a specific need, not hype. The result is a site that's fast, easy to manage, and fun to extend. Check out the source code on GitHub.

Support independent writing

If this post was useful, consider supporting my open source work and independent writing.

Comments

No comments yet. Be the first to share your thoughts.

2000 characters remaining