Mehmet Mutlu Portfolio
Overview
This is the site you're reading right now, mehmetmutlu.dev. I built it while job-hunting, and it's a live, working example of the same practices I'd bring to a team: modern Next.js patterns, real database-backed content instead of a static site generator, an admin CMS I actually use, and an agentic workflow for maintaining it that a Claude Code session (like the one that helped build a lot of it) can drive directly.
Stack
Next.js 16 (App Router, Turbopack) with React 19.2 and TypeScript throughout. Prisma 7 with the Neon serverless driver adapter, on Neon Postgres, with pgvector enabled from the first migration for a planned AI/RAG phase. Zod validates every content shape, from the admin form down to the CLI script. Styling is Tailwind CSS v4 with shadcn/ui primitives, restyled into a neobrutalist system: 2px borders, hard offset shadows, and a 5px radius, paired with Claude's own Design System colours (clay, oat neutrals, mineral, plum, cactus, heather) pulled from claude.ai's real stylesheet rather than approximated. Deployed on Vercel, with the domain on GoDaddy DNS.
Content model
Projects aren't hardcoded, they're rows in Postgres with a block-based content model: each project has an ordered list of typed blocks (heading, paragraph, image, YouTube embed, diagram, code, and generic embed), the same pattern used by most modern headless CMSs. A `category` field (commercial vs. personal) and a `yearRange`/`years` display override sit alongside the usual title, summary, role, and tags, so the same schema covers a six-month client engagement and a multi-year ongoing one without lying about either.
Sections
The homepage has a hero, an Impact stats strip, a career Journey timeline, a Selected Work grid (commercial projects only), a Personal Projects section further down (only rendered at all once there's something to show in it), an FAQ accordion, and a closing CV-download banner. `/projects` is the full filterable grid, filterable by technology or industry tag, commercial work sorted before personal work automatically. Each project has its own `/projects/[slug]` page rendering its block content. `/experience` is a static CV timeline, and `/cv` is a Route Handler that serves the actual PDF with a proper Content-Disposition header.
Admin
A password-gated `/admin` area, authenticated with a minimal signed HMAC-SHA256 cookie I wrote myself rather than pulling in an auth library, checked in Next.js 16's `proxy.ts` (the renamed middleware). Inside it: full project CRUD with a block editor UI (add/reorder/remove any block type, React 19's `useActionState`/`useOptimistic`/`useFormStatus` doing the heavy lifting), and a tag manager for creating and deleting the technology/industry tags used across the site.
The agentic layer
Alongside the browser-based admin, there's a plain CLI: `npm run project:upsert -- path/to/project.json` creates or updates a project directly against the live database, validated against the same Zod schemas as the admin form, no login or browser needed. It's intentionally not Claude-specific, any coding agent or I can run it by hand. A `PROJECTS.md` backlog file tracks what's live versus what's a real candidate, so an agent adding content picks from a vetted list rather than inventing project entries. Most of the content on this very site, including this project's own entry, was added this way, in conversation, verified, then upserted straight to production.
AI layer
The newest addition: a working AI layer, not a chat widget bolted on for show. Three surfaces share one retrieval pipeline: a chat panel that answers questions grounded in this site's own project and experience data, a job-fit checker that takes a pasted or uploaded (PDF/.docx/.txt) job description and returns a structured strengths/gaps analysis, and a remote MCP server exposing the same logic as tools (ask_about_mehmet, check_job_fit, get_cv) that Claude Desktop or Claude Code can call directly. Content gets chunked and embedded (OpenAI's text-embedding-3-small, 1536 dimensions) into a pgvector column on the same Postgres database, retrieved by cosine similarity, and fed into a grounded prompt. No separate vector database.
It's genuinely grounded, not just prompted to sound that way: the model is instructed to answer only from retrieved context and say so when it doesn't know, rather than guess. Testing it for real surfaced an actual accuracy bug worth mentioning honestly: a long job description could dilute similarity ranking enough that a true fact, like specific testing-framework experience, missed a flat top-8 retrieval cutoff and got reported as a false gap. The fix was to always include the full FAQ set unconditionally for fit-checks, alongside the top matching project and experience chunks, rather than trusting similarity ranking alone for a broad, multi-topic query.
The MCP server speaks the current 2026-07-28 spec via Vercel's own mcp-handler package, and it's not just wired up, it's been used: claude mcp add --transport http mehmet-portfolio https://mehmetmutlu.dev/api/mcp connects it for real, and I've called its tools live from Claude Code against production data, not just curl in a terminal.
Performance
Performance isn't an afterthought here, it's a Lighthouse-audited 100. First Contentful Paint at 0.3s, Largest Contentful Paint at 0.4s, 30ms of total blocking time, zero cumulative layout shift, and a 0.5s Speed Index, alongside a 94 on Accessibility and 100 on both Best Practices and SEO. Mostly Server Components, minimal client-side JavaScript, and Vercel's edge network doing the rest.