Back to projects
Featured projectproduction·May 19, 2026, 12:00 AM

AI-Powered Engineering Portfolio Platform

A personal portfolio built like a proper product — Next.js frontend, FastAPI backend, Postgres database, and a small CMS to manage everything from one place. It also has an AI assistant which answers questions about my work using only my own writeups, not random internet data.

Next.jsFastAPIReactTypeScriptPostgreSQLRAGAISemantic SearchpgvectorFull Stack

Overview

Product direction and engineering scope

Most developer portfolios are just a static site with a few cards and a contact form. I wanted mine to actually show how I build software end-to-end. So instead of hardcoding the projects, I built a full content platform behind it. The same backend serves the public pages, the dashboard where I write content, and the AI chat. Everything — projects, case studies, articles, architecture notes, experiments, research logs — lives in one shared table and goes through one pipeline. I can write in Markdown, hit publish, and the public page updates on its own through ISR. The AI assistant is grounded on my own content only. It reads my writeups, breaks them into chunks, stores embeddings, and uses RAG to answer. So when someone asks "what did he use for auth in this project", it actually pulls the answer from my notes, not from training data.

System details

Delivery notes and implementation specifics

Config-driven identity + shared content schema + Markdown-first authoring + grounded RAG assistant

Frontend

  • Next.js 16 App Router with React 19 and TypeScript
  • Public pages use ISR with a 5 minute revalidate window
  • Tailwind CSS for styling
  • Routes split cleanly between marketing (public site) and dashboard (CMS)

Backend

  • FastAPI with async SQLAlchemy on Postgres
  • Clean layering: routes → services → repositories → models
  • Alembic for migrations

Content model

  • One shared content_items table powers all six content types
  • Common fields: title, slug, body, SEO metadata, tags, categories
  • A JSONB metadata column holds type-specific extras like tech stack, GitHub link, live URL, etc.

CMS / Dashboard

  • Built inside the same Next.js app — no separate admin
  • Uses a same-origin BFF proxy at /api/dashboard/** which forwards to FastAPI admin routes
  • Drafts auto-save to localStorage so nothing is lost on refresh

Markdown pipeline

  • Single shared renderer — react-markdown + remark-gfm + rehype-highlight
  • Used by both the dashboard preview and the public pages
  • So what I see while writing is exactly what visitors see

AI assistant (RAG)

  • Knowledge documents are ingested into knowledge_documents
  • Split into knowledge_chunks with embeddings
  • Queries do semantic search, pick top chunks, and pass them as grounded context to the LLM
  • No hallucinated answers from outside my own content

Auth

  • Supabase-based auth on the dashboard side
  • Public pages stay open — no login wall

Deployment

LayerHost
FrontendVercel
APIRender
DatabaseManaged Postgres
Local devDocker Compose

Full writeup

Implementation notes

Why one shared content table

Earlier I had separate tables for projects, articles, case studies and so on. Very quickly it became a mess — duplicate columns, duplicate API routes, duplicate frontend components. So I collapsed everything into one content_items table with a type column and a JSONB metadata field for the type-specific stuff.

This one decision saved me a lot of work later. Now adding a new content type is basically:

  1. Add the type to the enum
  2. Register its known metadata keys
  3. Add a public route if needed

That's it. The dashboard form, the API, the search, the AI ingestion — all of it works automatically.

The Markdown-first decision

First version of the CMS had a rich text editor. Looked nice, but the HTML it produced was inconsistent and very hard to feed into the RAG pipeline. So I switched everything to Markdown.

Now the storage is raw Markdown. The dashboard shows a live preview using the same renderer that the public site uses. So drift is impossible — if it looks good in preview, it looks the same on the live page.

Markdown also works beautifully for RAG. The chunker can split on headings, keep code blocks intact, and the embeddings come out much cleaner than from messy HTML.

How the dashboard talks to the backend

Public pages hit FastAPI directly through server components. But the dashboard goes through a Next.js BFF (Backend For Frontend) layer at /api/dashboard/**. This way the browser never touches the admin API directly — auth cookies stay same-origin, CSRF is easier, and I can shape responses for the UI without changing the backend.

ISR for the public site

All public detail pages use ISR with revalidate = 300 and proper cache tags like content:project. So pages are basically static (super fast) but update within 5 minutes of any edit. I can also revalidate on demand from the dashboard if I want instant updates.

The RAG assistant

The flow is:

  1. Ingest — Markdown content is sent to the ingestion script which chunks it (heading-aware), generates embeddings, and stores them in knowledge_chunks.
  2. Retrieve — User question comes in, gets embedded, top-k similar chunks are pulled from Postgres using vector similarity.
  3. Ground — Those chunks are passed to the LLM as context with a strict prompt: "Answer only from the provided context. If not available, say so."
  4. Respond — Streamed response goes back to the user.

This way the assistant is actually useful for recruiters and engineers who want quick answers about my work, without me having to answer the same questions again and again.

Engineering discipline

Whole repo is a monorepo with apps/web, apps/api, packages/*. Typecheck, lint, build — all wired up. Tests on both frontend and backend. Migrations through Alembic. Local dev through Docker compose so it runs exactly the same way on any machine.

Nothing fancy, but everything is in the right place.