The Tech Stack Behind This Site
The Tech Stack Behind This Site

People keep asking me what this site is built with. Fair question — it's a portfolio for an AI builder, so the stack should reflect that. Here's the full breakdown.
Development: Claude Code + Skills
Every line of code on this site was written through Claude Code — Anthropic's CLI agent. I use specialized skills like senior-frontend and frontend-design for component scaffolding, styling, and architecture decisions. No traditional IDE workflow — just prompts, code reviews, and iterative refinement.
This isn't a gimmick. Claude Code handles everything from creating new components to debugging Prisma schema issues to pushing deployments. The entire development loop — write, test, commit, deploy — happens through the CLI.
Framework: Next.js 16 + React 19
Next.js 16 with the App Router gives me:
- Static Site Generation (SSG) for blog posts and project pages — fast loads, SEO-friendly
- Server Components by default — minimal client-side JavaScript
- API Routes for the admin CRUD endpoints
- TypeScript 5 throughout — type safety without the ceremony
The site is primarily static. Blog posts are pre-rendered at build time, which means every deploy bakes fresh content into the HTML.
Styling: Tailwind CSS 4 + Framer Motion
Tailwind CSS 4 handles all styling — no CSS modules, no styled-components. The new version brings:
- CSS-first configuration
- Better performance with the Oxide engine
- Native cascade layers
Framer Motion 12 powers the animations:
- Page transitions between routes
- Staggered entrance animations for lists
- Gradient mesh backgrounds
- Animated counters in the stats section
- Marquee ticker for the tools showcase
- Cursor glow effects
The visual design follows a clean, professional aesthetic with subtle depth — glass cards, gradient meshes, and smooth transitions that feel polished without being distracting.
Database: Turso (Cloud SQLite)
Turso is a cloud-hosted SQLite database built on libSQL. Why Turso over Postgres or PlanetScale?
- Edge-ready — SQLite at the edge with global replication
- Generous free tier — more than enough for a portfolio site
- Simple — it's just SQLite with a URL
The database lives in aws-us-west-2 and connects via the @libsql/client adapter.
A Turso Gotcha
One thing I learned: Prisma's orderBy doesn't reliably work with the Turso adapter. My blog posts were showing in insert order instead of date-descending. The fix: always sort in JavaScript after fetching, regardless of what the ORM says it's doing.
function sortPostsByDate(posts: PostData[]): PostData[] { return posts.sort((a, b) => { const da = a.publishedAt ? new Date(a.publishedAt).getTime() : 0; const db = b.publishedAt ? new Date(b.publishedAt).getTime() : 0; return db - da; }); }
Trust but verify your ORM.
ORM: Prisma 7
Prisma 7 with the @prisma/adapter-libsql bridge to Turso. Six models:
| Model | Purpose |
|---|---|
| Post | Blog posts with markdown content |
| Project | Portfolio projects with descriptions |
| TimelineEvent | Journey milestones |
| Tool | Tools and technologies evaluated |
| Experience | Career history |
| SiteSetting | Key-value config |
Data Architecture: DB-First with Static Fallback
This is the pattern I'm most proud of. Every data-fetching function follows the same structure:
- Try the database first — query Turso via Prisma
- Fall back to static constants — if DB is empty or errors, use
SAMPLE_*arrays hardcoded inconstants.ts
export async function getPosts(): Promise<PostData[]> { try { const posts = await prisma.post.findMany({ where: { published: true } }); if (posts.length > 0) return sortPostsByDate(posts); } catch {} return sortPostsByDate(SAMPLE_POSTS.filter(p => p.published)); }
This means the site never breaks even if Turso is down. The static fallback is always there. The tradeoff: both sources must stay in sync when adding content.
Auth: Simple Bearer Token
No OAuth, no JWT, no auth library. The admin API uses a single Bearer token:
- Local development:
admin123from.env - Production:
ADMIN_PASSWORDenvironment variable on Vercel
There's no user-facing authentication — it's a portfolio site, not a SaaS product. The admin routes are just for me to manage content via API calls.
Hosting: Vercel
Vercel handles hosting with zero configuration:
- GitHub integration — push to
maintriggers auto-deploy - Edge network — static assets served from the nearest PoP
- Environment variables —
TURSO_DATABASE_URL,TURSO_AUTH_TOKEN,ADMIN_PASSWORD
A Vercel Gotcha
Vercel's build cache can serve stale CSS bundles. I had a full visual overhaul (gradient meshes, glass cards, animated counters) that deployed successfully but the old styles kept showing. The fix: push an empty commit to force a clean rebuild.
git commit --allow-empty -m "Force rebuild: clear Vercel cache" git push origin main
Always compare deployed CSS file size against your local build to verify.
Markdown Rendering
Blog content is stored as raw Markdown and rendered with:
- react-markdown — base renderer
- remark-gfm — GitHub Flavored Markdown (tables, strikethrough, task lists)
- rehype-raw — allows raw HTML in markdown (for embeds)
- shiki — syntax highlighting with VS Code-quality themes
The Full Picture
| Layer | Tech |
|---|---|
| Development | Claude Code + frontend skills |
| Framework | Next.js 16 (App Router, SSG) |
| Language | TypeScript 5, React 19 |
| Styling | Tailwind CSS 4 + Framer Motion 12 |
| Database | Turso (cloud SQLite / libSQL) |
| ORM | Prisma 7 |
| Auth | Bearer token (admin only) |
| Hosting | Vercel (auto-deploy from GitHub) |
| Markdown | react-markdown + remark-gfm + rehype-raw + shiki |
| Data pattern | DB-first with static fallback |
The whole thing is intentionally simple. No overengineering, no premature abstractions. A portfolio site doesn't need a microservices architecture — it needs fast loads, good content, and reliable deploys.
The most interesting part isn't any individual technology choice. It's that the entire site — every component, every API route, every blog post — was built through conversation with an AI agent. The stack is the proof that AI-native development isn't a future promise. It's how I work today.