An earlier personal portfolio built to present data analysis case studies as structured, searchable content — static frontend, validated data pipeline, and one serverless route for geo IP checks.
Problem
I needed a portfolio for presenting data analysis work and project visualizations without running a CMS or database. Content stays in validated JSON, deploys as a static SPA, and only uses backend code where a server-side secret is required.
Frontend work
- React 19 + TypeScript + Vite SPA with lazy-loaded routes
- Theme UI design system with light/dark mode
- Searchable and filterable project grid
- Project detail pages with case-study structure and plot carousel
- Static content pipeline using JSON, Zod schemas, and Vite asset resolution
- Contact page with React Hook Form, Getform submission, clipboard copy, and resume download
- SEO metadata with react-helmet-async, sitemap, robots, and Vercel Speed Insights
- Accessibility details including skip link, reduced-motion styles, and ARIA feedback states
Backend & system work
- Minimal Vercel serverless handler for GET /api/geo-block
- Abstract API IP geolocation used server-side so the API key stays private
- Vercel routing that sends /api/* to the serverless handler and the rest to the static frontend
- Client-side Getform submission for contact messages
Key decisions
- Used JSON + Zod instead of a CMS/database to keep content git-reviewable and statically deployable
- Kept backend scope intentionally minimal — only serverless code where secrets must remain server-side
- Used Zustand as a lightweight access layer over validated static content
- Lazy-loaded routes and heavier UI pieces to keep the SPA structure cleaner
- Used Vercel to deploy a static frontend and one serverless route from the same repo
Challenges
- Keeping content integrity without a CMS
- Resolving local asset references into Vite build output
- Coordinating static frontend routing with a minimal serverless backend
- Keeping Theme UI and React Bootstrap components visually consistent
- Making geo-blocking fail open so the site still renders when the external API is unavailable
Tech stack
Frontend
- React 19
- TypeScript
- Vite
- React Router
- Theme UI
- React Bootstrap
- Framer Motion
- Zustand
- Zod
- React Hook Form
- react-helmet-async
Backend
- Vercel serverless Node handler
Data
- Local JSON content files
Infrastructure
- Vercel
- Vercel Speed Insights
Tooling
- Bun
- TypeScript
- Vite
Browser APIs
- Clipboard API
- fetch