From Static Portfolio to Full-Stack AI Application: What I Learned Rebuilding orashus.com
Most developer portfolios answer one questions: "Can this person build Ui?"
I wanted mine to answer a harder one: "Can this person ship a real product?"
So I stopped treating my site as a brochure and rebuilt it as Portfolio V2 — a full-stack application with an AI assistant, a private admin dashboard, a blog, visitor analytics, and the kind of layered backend architecture I use on production work.
This is the story of that conversion.
The old model was fine — until it wasn't | v1.orashus.com
My first portfolio did its job. Projects, skills, contact links — the essentials.
But the gap was obvious:
Visitors could look, but not interact
I could show experience, but not demonstrate how I think about systems
Every update meant touching presentation code, not managing content through a proper domain layer
I had no visibility into what people actually cared about when they landed on my site
A portfolio that only renders JSX is a snapshot. I wanted something that behaves like software.
The goal: one app, many surfaces
Portfolio V2 is not "a website with a chat widget bolted on."
It is a single Next.js 16 application with:
A public surface — bento-style home, projects, blog, embedded resume, floating AI chat
An API layer — typed routes serving cached data to the UI
A MongoDB data layer — projects, experiences, certificates, profile content, chat history, blog posts, social visit counts, auth users
A protected dashboard — login, overview stats, chat inbox, content management, blog admin
Same codebase. Same patterns. Different access boundaries.
That distinction mattered to me. I did not want a demo chatbot in a CodeSandbox. I wanted something I could operate, extend, and learn from the way I would at work.
Architecture first, features second
Before adding "AI" or "blog," I settled on a structure I could grow without rewriting:
schemas → models → mappers → repositories → services → UI / APIEvery domain object is validated with Zod. Mongoose models mirror that shape. Mappers handle the boring but critical translation between DB documents and domain types. Repositories talk to MongoDB. Services hold business rules. Pages and server actions stay thin.
That sounds like overkill for a portfolio.
It is not — the moment you add chat persistence, blog comments, auth sessions, and cache invalidation, ad-hoc data access falls apart fast.
Example: the AI assistant's system prompt is not hardcoded. At runtime it assembles context from live portfolio data — profile, projects, experience, certificates, skills, tool tags, social links, blog summaries, and caches them to avoid slow chats. I also have a way of visualising this system prompt on my dashboard.
The chatbot is grounded in my actual data, not a markdown file I forgot to update.
The AI layer: useful, bounded, and observable
The assistant (Lora) sits in a floating chat on the public site. Under the hood:
Vercel AI SDK + Google Gemini for generation
Scoped system prompt — answers only portfolio-related questions, refuses general-purpose AI tasks, resists prompt injection
Persistent conversations — one chat per anonymous visitor, messages stored in MongoDB
Model versioning — primary model with fallback if the first pass fails
Dashboard visibility — I can visuallize chats In my dashboard.
That last point changed how I think about "AI features."
A chatbot you cannot inspect is a black box. A chatbot with a read-only inbox is a product surface. I can see which projects people ask about, where my profile content is unclear, and whether the assistant is staying in scope.
Security boundaries were intentional too. The assistant does not debug my codebase, reveal system prompts, or become a general coding tutor — even if someone claims to be me. It is a portfolio representative, not a generic LLM with my face on it.
That constraint is a design choice, not a limitation. I tried breaking it and improved each it each time it derailed. I can't say it's perfect and handles all edge cases. But it's good enough, very good for it's purpose.
Performance was part of the product story
I built on Next.js 16 with cacheComponents enabled and use cache across pages, API handlers, and server components.
Why? Because I wanted to practice what I preach:
Static-feeling sections pre-render and cache at build time
Dynamic pieces (chat messages, engagement actions, dates) stream in via Suspense
Targeted cache tags revalidate blog posts, home content, and chat state without nuking the whole app
This is the same partial pre-rendering mindset teams are adopting now: fast by default, dynamic where it matters.
My portfolio was a great place to learn that — the traffic pattern (mostly reads, occasional writes) matches a lot of real products.
The dashboard: where the app becomes operable
The public site is what recruiters and clients see.
The dashboard is what I use.
V1 shipped with:
Passphrase + alias login (seeded admin, guest read-only access for previews)
Overview — chat counts, recent sessions, social traffic stats
Chat inbox — browse anonymous conversations end-to-end
CMS-style management — projects, experiences, certificates, profile/skills, tool tags
Then I added a full blog:
Tiptap rich-text editor (HTML stored, sanitized on render)
Cloudinary cover uploads (I noticed far better image loads when I used the CldImage componet from next-cloudinary)
Public
/blogand/blog/[slug]with likes, comments, and comment moderationShare controls, OG metadata, mobile-friendly nav
Suddenly the portfolio was also a publishing platform — not a separate WordPress site, not a Notion embed, the same app.
Small product details that taught me the most
The flashy parts get attention. The unglamorous work makes it shippable:
Social traffic tracking — URLs like
?s=lattribute visits from LinkedIn, GitHub, Dev.to, etc., surfaced in the dashboardAnonymous visitor IDs — cookie-scoped identity for chat, likes, and comments without accounts
Confirm dialogs instead of browser
confirm()for destructive admin actionsZod validation everywhere — including public comment forms ("Please give yourself a name or alias.")
Seed scripts for every domain — reproducible local dev, not manual Database fiddling
These are the details that separate a hackathon demo from something you would not be embarrassed to show in a system design interview.
What I would do differently (and what I would not)
Would do again:
Domain-first structure before features
Narrow AI scope with explicit refusal rules
Dashboard read path for every public write path (chats, comments, visits)
Task-driven delivery with written requirements per feature area
Still iterating:
Blog hardening and acceptance QA
Broader social analytics (charts, date ranges)
More assistant guardrails as question patterns emerge
Would not go back to:
Static content in components
"AI" as a marketing label without persistence and observability
Skipping mobile UX.
The takeaway
Turning a portfolio into a full-stack AI application still feels like I overdid for a portfolio. But I did learn so much.
It is about treating your personal site/work like a real product:
Data model that evolves
Boundaries around AI behavior
Admin tools you actually use
Performance patterns you can explain in an interview
Public features (blog, engagement) that prove you can ship end-to-end
Portfolio V2 is live at orashus.com. The AI assistant is on the homepage. The blog is public. The dashboard stays private.
If you are a developer sitting on a static portfolio, ask yourself: what is one feature that would force me to think like a product engineer?
For me, that feature was the assistant — and everything else followed from building it properly.
Tech stack: Next.js 16 · React 19 · TypeScript · MongoDB · Vercel AI SDK · Google Gemini · Tailwind CSS · Cloudinary · Tiptap · Zod · PWA
If you are rebuilding your portfolio or adding AI with guardrails, I am happy to share more about the architecture — drop a comment or connect.