Case StudyJanuary 24, 202610 min read

> Building a Modern Portfolio with Next.js 15, React 19 & Custom CMS

A deep dive into building my personal portfolio website with a hacker-themed UI, custom content management system, and VPS deployment. Learn the architecture, challenges, and solutions.

## Introduction After years of building websites for clients, I finally decided to create my own portfolio - and I wanted it to be special. Not just another template, but something that truly represents my personality and skills as a developer. In this case study, I'll walk you through how I built **aadilkhan.site** - a full-stack portfolio featuring a cyber/hacker aesthetic, custom CMS, and professional deployment on my own VPS. --- ## The Vision I had specific goals for this project: 1. **Unique Design** - A cyber/hacker theme that stands out 2. **Full Control** - Custom CMS to update content without redeployment 3. **Performance** - Instant page loads, even on slow connections 4. **Professional Deployment** - Self-hosted on VPS, not relying on free tiers --- ## Tech Stack Overview ### Frontend - **Next.js 15** - Latest version with App Router - **React 19** - Server and client components - **TypeScript** - Type-safe development - **Tailwind CSS** - Utility-first styling ### Backend - **Node.js + Express** - RESTful API - **MongoDB Atlas** - Cloud database - **Cloudflare R2** - Image storage ### Deployment - **Nginx** - Reverse proxy and static file serving - **PM2** - Node.js process manager - **Let's Encrypt** - SSL certificates --- ## The Hacker Theme The design was inspired by classic terminal aesthetics and cyberpunk visuals. Key elements include: ### Matrix Rain Animation I implemented a canvas-based matrix effect that runs in the background: ```javascript useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const chars = '01アイウエオカキクケコ'; const fontSize = 14; const columns = canvas.width / fontSize; const drops = Array(columns).fill(1); const draw = () => { ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#0f0'; drops.forEach((y, i) => { const char = chars[Math.floor(Math.random() * chars.length)]; ctx.fillText(char, i * fontSize, y * fontSize); if (y * fontSize > canvas.height && Math.random() > 0.975) { drops[i] = 0; } drops[i]++; }); }; const interval = setInterval(draw, 33); return () => clearInterval(interval); }, []); ``` ### Terminal-Inspired UI Every element uses the cyber aesthetic: - Green-on-black color scheme (`#4ade80` on `#000`) - Monospace fonts - Command prompt prefixes (`$`, `>`) - Animated cursor effects --- ## Hybrid Caching Strategy One major challenge was dealing with cold starts on free hosting tiers. My solution: **hybrid caching**. ### The Problem When using Render.com's free tier, the server sleeps after inactivity. First visits take 30-50 seconds to wake up. ### The Solution 1. **Build-time data fetching** - Fetch latest data during deployment 2. **Static baking** - Include data in the deployed bundle 3. **Client-side caching** - Store in localStorage for repeat visits 4. **Background refresh** - Silently update stale data ```typescript // Hybrid data hook export function usePortfolioData() { const [data, setData] = useState(defaultData); const [fromCache, setFromCache] = useState(true); useEffect(() => { // Try cache first const cached = localStorage.getItem('portfolio-data'); if (cached) { setData(JSON.parse(cached)); } // Fetch fresh in background fetch('/api/data') .then(res => res.json()) .then(fresh => { setData(fresh); setFromCache(false); localStorage.setItem('portfolio-data', JSON.stringify(fresh)); }); }, []); return { data, fromCache }; } ``` Result: Users see content instantly, even if backend is cold. --- ## Custom CMS Development Instead of using WordPress or a headless CMS, I built my own admin panel: ### Features - **Projects management** - CRUD with image uploads - **Blog posts** - Markdown content with preview - **Skills & Tools** - Dynamic updates - **Contact messages** - View and manage inquiries - **Real-time preview** - See changes before publishing ### Admin Panel Stack - React with React Router - Vite for fast development - React Hook Form for forms - Toast notifications ### Image Upload Flow 1. User selects/drops image 2. Frontend compresses if needed 3. Upload to Express `/api/upload` 4. Server uploads to Cloudflare R2 5. Return public URL to store in database --- ## VPS Deployment Journey Deploying to a VPS was a learning experience. Here's what I set up: ### Server Architecture ``` Internet → Nginx (Port 80/443) │ ├── aadilkhan.site → /frontend/out (static) ├── admin.aadilkhan.site → /admin/dist (static) └── api.aadilkhan.site → localhost:5005 (proxy) ``` ### Nginx Configuration ```nginx server { listen 80; server_name aadilkhan.site; root /home/admin2/portfolio/frontend/out; index index.html; location / { try_files $uri $uri.html $uri/ /index.html; } } ``` ### PM2 for Node.js ```bash pm2 start server.js --name "portfolio-api" pm2 startup # Auto-start on reboot pm2 save ``` ### SSL with Let's Encrypt ```bash sudo certbot --nginx \ -d aadilkhan.site \ -d admin.aadilkhan.site \ -d api.aadilkhan.site ``` --- ## Challenges & Solutions ### 1. CORS Issues **Problem**: Frontend on aadilkhan.site couldn't reach api.aadilkhan.site **Solution**: Proper CORS configuration with specific origins: ```javascript app.use(cors({ origin: [ 'https://aadilkhan.site', 'https://admin.aadilkhan.site' ], credentials: true })); ``` ### 2. Permission Denied (Nginx) **Problem**: 500 errors when serving static files **Solution**: Fix directory permissions: ```bash chmod 755 /home/admin2 chmod -R 755 /home/admin2/portfolio ``` ### 3. Cold Start Delays **Problem**: 40+ second waits on first load **Solution**: Hybrid caching + VPS migration (now instant!) --- ## Performance Metrics After optimization: - **First Contentful Paint**: < 1s - **Largest Contentful Paint**: < 2s - **Time to Interactive**: < 1.5s - **PageSpeed Score**: 90+ --- ## Key Takeaways 1. **Own your infrastructure** - VPS gives you control and eliminates cold starts 2. **Design with purpose** - The hacker theme isn't just aesthetic, it's memorable 3. **Build for offline** - Caching ensures users always see content 4. **Custom CMS freedom** - Full control over your content structure 5. **Document everything** - Future you will thank present you --- ## What's Next? Future improvements planned: - Blog comments system - Dark/light theme toggle - Project filtering by technology - Analytics dashboard - RSS feed for blog --- ## Conclusion Building this portfolio was more than just a project - it was a complete full-stack journey from design to deployment. If you're a developer considering building your own portfolio, I highly recommend going the custom route. Yes, it takes more time, but the learning experience and final result are worth it. The site is live at **[aadilkhan.site](https://aadilkhan.site)** - feel free to explore and reach out! --- *Have questions about the implementation? Drop me a message through the contact form or connect on [LinkedIn](https://linkedin.com/in/aadilkhan00).* ``` --- ## ✅ Quick Copy Checklist When uploading to admin panel, make sure to: - [ ] Title entered - [ ] Slug is URL-friendly (no spaces) - [ ] Category: "Case Study" - [ ] Preview written (under 200 chars) - [ ] Read Time: "15 min read" - [ ] Content pasted (full markdown) - [ ] isPublished: true - [ ] Order: 1 (to show first)