Why I Built It From Scratch
An engineer's portfolio has mostly become a "fill in the template" job these days. WordPress, ready-made portfolio platforms, Notion pages — fast and functional. But I wanted something different: a full-stack site containing my own system, my own architectural decisions, and even my own AI chatbot.
The reasons are clear:
- Not a showcase, but proof: A site that shows what I can do rather than telling about it.
- A learning ground: My own production deployment, my own cache strategy, my own auth layer.
- A working thing: Site visitors can talk to my chatbot — and this architecture is the same RAG pipeline I developed in the Mavi Çatal project.
Two and a half weeks ago there was zero code. Today, it's live.
Stack Choices and Reasoning
Every technology has a "why I chose this" story:
Backend — .NET 10 + EF Core 10: C#'s type system and ASP.NET's middleware pipeline offer elegant tools for modern API engineering. The Result<T> pattern and ProblemDetails keep error management disciplined. Nullable reference types and source generators give refactoring flexibility.
Frontend — Next.js 16 + Tailwind 4: The SSR + ISR + Server Components trio makes sense for both SEO and performance. Tailwind 4's @theme directive enables design-token-based work — I can manage all colors and spacing from one place.
AI — Python + LangGraph + pgvector: "A single LLM call" wasn't enough for the chat bot. RAG retrieval, query rewriting, tool calling, streaming — modeling these on a graph is much cleaner than cramming them into one prompt. LangGraph's state machine approach lets me think about flow visually, not just in code.
Database — PostgreSQL + pgvector: Embeddings and regular data in one place. No need to set up a separate vector database (Pinecone, Weaviate).
Deploy — DigitalOcean + Supabase + Vercel: Frontend free on Vercel's Hobby tier, DB Supabase free tier is sufficient, backend + chat bot on my own VPS — systemd + nginx + Let's Encrypt. Monthly cost: about $20.
Lesson 1: Removing the AI Grader Solved Hallucination
The first version of my chat bot had an "advanced" RAG architecture:
- Rewrite the query
- Generate embeddings
- Retrieve top-k chunks
- Have an LLM grade "is this chunk really relevant?" (grader)
- Generate the answer with only relevant chunks
Sounds reasonable, right? Better context = better answer.
In practice: the grader was too strict. For the question "What is Mahmoud's most successful project?", chunks mentioning LoomAI were retrieved, but the grader rejected them because they "don't contain the exact words 'most successful'." Left with empty context, the LLM made things up — hallucination began.
The fix: I removed the grader entirely. Just retrieve → generate. Result: 10/10 specific project names correctly answered, zero hallucination.
Lesson: "More AI layers = better results" isn't automatically true. Sometimes less wins. In architectural decisions, measure first, then talk.
Lesson 2: Going to Production — 12 Bugs Trained Me
Building the site is one game; taking it to production is another. I bumped into 12 different issues. Some of them:
- Supabase's IPv6: My VPS doesn't have IPv6, direct connection was failing. Fix: Session Pooler.
- JSON parse error:
appsettings.Production.jsonwouldn't load becauseopenssl rand -base64output had a newline. Cleaned with| tr -d '\n'. - pgvector wrong schema: It was installed in the
extensionsschema, mypublic.vector(1536)reference couldn't find it. Fixed withDROP EXTENSION; CREATE EXTENSION ... WITH SCHEMA public;. - CSP block: Frontend's
connect-srcdirective only had localhost. Got a "Something went wrong" error on login. #in the password: My Supabase password had#, which.envfiles interpret as a comment character. URL-encoded it (%23).
Lesson: Going to production always takes longer than you predict. Because it's not "computer science" — it's production engineering — things that don't appear in the lab, only in the real world.
Lesson 3: I Used the Same Architecture in Two Places
My chat bot's infrastructure is the same RAG pipeline I previously developed in the Mavi Çatal project (restaurant AI assistant). Instead of rewriting it, I made it generic:
- Knowledge base is a text file, regenerable
- Chunking parameters in config
- Embedding model is pluggable
- System prompts separated as TR/EN
Result: No need to start from scratch for a new RAG project.
Lesson: When you copy something to a second place, you realize the architecture wasn't placed correctly. Refactoring time at the third place.
Lesson 4: Admin Panel Should Exist From Day One
Without the site's Admin panel, I was either writing SQL directly or running seed files to add content. When the Admin panel went live in Phase 8, content production speed tripled — adding a blog post is now far easier than writing SQL.
Lesson: "Build the product first, panel later" thinkers — myself included. Wrong. Producing content without a panel is painfully slow and demotivating.
Lesson 5: Polyglot Stack, Not Microservices
Frontend Next.js, backend .NET, AI Python — three different languages, three different runtimes. At first I worried: "too fragmented?"
But I experienced that each is the best at what it does:
- Next.js → top of class for web UI and SSR
- .NET → mature for enterprise API and type safety
- Python → undisputed for the AI/ML ecosystem
Instead of trying to write everything in one language, I gave each responsibility to the most appropriate stack. This is not microservices — it's one product, three languages. We could call it a "polyglot monolith."
Lesson: Not microservices, not monolith; right tool for the right job.
What's Next
The site is live but not done. Up next:
- More blog posts — this was the first
- Enrich the EN knowledge base with real translations (currently a twin of TR)
- Set up UptimeRobot monitoring
- Measure performance with real user metrics
Feedback is always welcome. Ask the chatbot in the bottom-right "how's the site?", or email me directly: mahmuod.almuhammad@gmail.com.
Thanks for reading — I hope you found something useful for your own project.
