February 22, 2026 · My Learnings · 5 min read
A walkthrough of the architecture, tech stack, and key decisions behind this portfolio — Next.js, Prisma, Tailwind, and AI-powered features.
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.
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.
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.
The Profile and SiteSettings models use a fixed ID of "main", always exactly one row. Simple to query, impossible to accidentally duplicate.
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.
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.
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.
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.
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.
tsvector would be natural.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.
If this post was useful, consider supporting my open source work and independent writing.
No comments yet. Be the first to share your thoughts.