vivekmahendra.com
Personal website with markdown-based content and interactive visualizations
Technologies:
Personal Portfolio Website
Note: This article was AI-generated based on the project directory structure and codebase to provide comprehensive technical documentation.
This is my personal portfolio website built with modern web technologies, focusing on performance, maintainability, and clean design. The site serves as both a showcase of my work and a platform for sharing ideas through a built-in blog.
Architecture Overview
The application is built on React Router v7 (the evolution of Remix), providing full-stack capabilities with file-based routing and server-side rendering. The content management system uses MDX files for blog posts and project descriptions, allowing for rich content with embedded React components.
The design philosophy emphasizes minimalism and performance:
- Clean, professional typography using system fonts
- Tailwind CSS for consistent, utility-first styling
- Subtle animations that enhance UX without being distracting
- Mobile-first responsive design
- Fast build times and optimal loading performance
Key Technical Features
Content-First Architecture
All blog posts and project descriptions are written in MDX, stored in the /content
directory. This approach provides the best of both worlds: the simplicity of Markdown with the power of React components for interactive elements like charts and code snippets.
Component Organization
The codebase follows a clear component hierarchy:
/layouts
- Reusable layout components/home
- Home page specific sections/ui
- General UI components/mdx
- Components designed for use within MDX content/react-bits
- Third-party animated components
Performance Optimizations
- Vite for fast development and optimized production builds
- Dynamic imports for code splitting
- Responsive images and lazy loading
- Minimal JavaScript bundle size
Core Implementation Examples:
1// Main app layout with mobile-responsive navigation2export function Layout({ children }: { children: React.ReactNode }) {3const location = useLocation();4const [mobileMenuOpen, setMobileMenuOpen] = useState(false);5 6return (7 8<div className="min-h-screen bg-white text-black flex flex-col">9<header className="border-b border-gray-200">10{/* Desktop Navigation */}11<nav className="hidden md:flex space-x-8">12{navigation.map((item) => (13<Link14to={item.href}15className={`transition-colors ${16location.pathname === item.href17? "text-black"18: "text-gray-500 hover:text-black"19}`} >20{item.name}21</Link>22))}23</nav>24 25 {/* Animated mobile menu with staggered transitions */}26 <div className={`md:hidden overflow-hidden transition-all duration-300 ${27 mobileMenuOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'28 }`}>29 {/* Menu items with cascading animation delays */}30 </div>31 </header>32 33 <main className="flex-grow">{children}</main>34 <footer className="border-t border-gray-200 mt-auto">35 Made in Phoenix 🌵36 </footer>37 </div>38 39);40}
The layout component demonstrates several key patterns: a sticky header with conditional styling based on the current route, an animated mobile menu with staggered transitions, and a flexbox layout that ensures the footer stays at the bottom. The mobile menu uses CSS transforms and opacity for smooth animations.
1// Dynamic content loading with TypeScript support2export function getIdeas() {3const ideas = import.meta.glob('../content/ideas/*.mdx', { eager: true });4 5return Object.entries(ideas)6 .map(([path, module]) => ({7 slug: path.replace('../content/ideas/', '').replace('.mdx', ''),8 title: module.frontmatter.title,9 date: module.frontmatter.date,10 excerpt: module.frontmatter.excerpt,11 }))12 .sort((a, b) => new Date(b.date) - new Date(a.date));13}14 15// Route loader for individual posts16export async function loader({ params }) {17const { slug } = params;18const content = await import(`../content/ideas/${slug}.mdx`);19 20return {21content: content.default,22frontmatter: content.frontmatter23};24}
This content management system uses Vite's import.meta.glob
to automatically discover and import MDX files at build time. The frontmatter is extracted for metadata like titles and dates, while the content is dynamically loaded in route loaders. This approach eliminates the need for a traditional CMS while maintaining type safety.
1// Modern build setup with MDX and ngrok support2export default defineConfig({3plugins: [4 tailwindcss(), 5 mdx({6 jsxImportSource: "react",7 providerImportSource: "@mdx-js/react",8 remarkPlugins: [9 remarkFrontmatter,10 [remarkMdxFrontmatter, { name: "frontmatter" }]11 ],12 }),13 reactRouter(), 14 tsconfigPaths()15],16server: {17 allowedHosts: [18 "localhost",19 ".ngrok-free.app", // Support for ngrok tunneling20 ".ngrok.io"21 ]22}23});
The Vite configuration showcases the modern build pipeline. The MDX plugin handles frontmatter extraction and JSX compilation, while the ngrok allowedHosts configuration enables easy local development with external access for testing on mobile devices.
1// LetterGlitch background with DecryptedText header2import { LetterGlitch, DecryptedText } from "../components/react-bits";3 4export default function Home() {5return (6 <div className="bg-white text-black overflow-x-hidden">7 {/* Full-width animated background */}8 <div className="relative overflow-hidden">9 <div className="absolute inset-0 z-0">10 <LetterGlitch11 glitchColors={["#f0f0f0", "#f4f4f4", "#eeeeee"]}12 glitchSpeed={500}13 smooth={true}14 outerVignette={false}15 centerVignette={false}16 />17 </div>18 19 <div className="container mx-auto px-6 relative z-10">20 <h1 className="text-5xl sm:text-6xl lg:text-7xl font-extralight">21 <DecryptedText22 text="Hello, I'm Vivek"23 animateOn="view"24 revealDirection="start"25 speed={100}26 />27 </h1>28 </div>29 </div>30 </div>31 32);33}
The home page demonstrates layered animations using React Bits components. The LetterGlitch provides a subtle animated background that doesn't interfere with readability, while DecryptedText creates an engaging reveal effect for the main heading. The overflow-x-hidden
class prevents horizontal scrolling issues on mobile.
1// Simple, consistent page headers across all routes2interface PageHeaderProps {3title: string;4}5 6export function PageHeader({ title }: PageHeaderProps) {7return (8 <div className="mb-16">9 <h1 className="text-4xl font-light">{title}</h1>10 </div>11);12}13 14// Usage in route components15import { PageHeader } from "../components/layouts";16 17export default function About() {18return (19 <div className="bg-white text-black">20 <div className="container mx-auto px-6 py-20">21 <PageHeader title="About" />22 {/* Page content */}23 </div>24 </div>25);26}
This demonstrates the DRY principle applied to UI components. Rather than repeating header markup across pages, a single reusable component ensures consistency. The interface provides type safety while keeping the API minimal. This pattern makes global design changes simple to implement.
1// Consistent hover animations across all arrow links2<Link 3to="/projects"4className="group px-4 py-1 text-sm border border-gray-300 rounded-full hover:bg-gray-50 transition-colors"5>6View all <span className="inline-block transition-transform group-hover:translate-x-1">→</span>7</Link>8 9<Link 10to={`/ideas/${post.slug}`}11className="group text-sm text-gray-600 hover:text-black transition-colors inline-block"12>13View Article <span className="inline-block transition-transform group-hover:translate-x-1">→</span>14</Link>15 16// Applied consistently across home sections, project cards, and article links
These micro-interactions add polish without overwhelming the user. The arrow translate animation provides immediate feedback on hover, while the group/group-hover pattern ensures the animation only affects the arrow, not the entire link. This creates a cohesive experience across all navigation elements.
1// Adding celebration effects with canvas-confetti2import confetti from "canvas-confetti";3 4export function Newsletter() {5const [showSuccessModal, setShowSuccessModal] = useState(false);6 7// Trigger confetti when success modal opens8useEffect(() => {9if (showSuccessModal) {10confetti({11particleCount: 50,12spread: 60,13origin: { y: 0.6 },14disableForReducedMotion: true,15colors: ['#FFD700', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57']16});17}18}, [showSuccessModal]);19 20return (21 22<Modal23isOpen={showSuccessModal}24onClose={() => setShowSuccessModal(false)}25title="Successfully Subscribed! 🎉"26>27{/* Modal content */}28</Modal>29); }
The newsletter subscription includes a delightful confetti effect using canvas-confetti. When users successfully subscribe, the celebration animation enhances the positive feedback while respecting accessibility preferences through the disableForReducedMotion
option. The confetti colors are carefully chosen to match the festive party emoji aesthetic, creating a cohesive celebratory experience.
Development Experience
The development setup prioritizes fast feedback loops and maintainability:
- Hot module replacement for instant updates during development
- TypeScript throughout for type safety and better IDE support
- Consistent code organization making features easy to locate and modify
- Component-driven development enabling rapid UI iteration
Performance Considerations
The site achieves excellent Core Web Vitals through several optimizations:
- Server-side rendering for fast initial page loads
- Minimal JavaScript bundle sizes through code splitting
- Optimized images and lazy loading
- Efficient CSS delivery via Tailwind's purging
This architecture strikes a balance between developer experience and user performance, making it both enjoyable to work with and fast for end users.
Deployment with Google Cloud Run
The application is deployed on Google Cloud Run, Google's fully managed serverless container platform. Cloud Run provides the perfect balance of simplicity, scalability, and cost-effectiveness for modern web applications.
Why Google Cloud Run?
Cloud Run offers several compelling advantages for this portfolio site:
- Serverless Architecture: No servers to manage or maintain - Cloud Run handles all infrastructure automatically
- Scale to Zero: When there's no traffic, the service scales down to zero instances, meaning you only pay for actual usage
- Automatic Scaling: Seamlessly handles traffic spikes by automatically scaling up to handle thousands of concurrent requests
- Container-Based: Deploy any containerized application without vendor lock-in
- Built-in HTTPS: Automatic SSL certificates and secure connections out of the box
- Global Load Balancing: Requests are automatically routed to the nearest region for optimal performance
The Deployment Architecture
The deployment pipeline uses a multi-stage approach for optimal efficiency:
1# Build stage - compile and build the application2FROM node:20-alpine AS builder3WORKDIR /app4 5# Install dependencies with legacy peer deps for React 19 compatibility6 7COPY package*.json ./8RUN npm ci --legacy-peer-deps9 10# Build the application11 12COPY . .13RUN npm run build14 15# Production stage - lightweight runtime image16 17FROM node:20-alpine18 19# Add signal handling for graceful shutdowns20 21RUN apk add --no-cache dumb-init22 23# Create non-root user for security24 25RUN addgroup -g 1001 -S nodejs && \26adduser -S nodejs -u 100127 28# Copy only production dependencies and built files29 30WORKDIR /app31COPY package*.json ./32RUN npm ci --only=production --legacy-peer-deps33 34# Copy built application from builder stage35 36COPY --from=builder --chown=nodejs:nodejs /app/build ./build37 38# Run as non-root user39 40USER nodejs41 42# Cloud Run automatically sets the PORT environment variable43 44ENV PORT=300045 46# Start with proper signal handling47 48ENTRYPOINT ["dumb-init", "--"]49CMD ["sh", "scripts/start.sh"]
This multi-stage build approach reduces the final image size by over 60%, keeping only the production dependencies and compiled assets. The use of Alpine Linux further minimizes the footprint, while dumb-init
ensures proper signal handling for graceful shutdowns in the containerized environment.
Automated CI/CD with Cloud Build
The deployment process is fully automated using Google Cloud Build, triggered on every push to the main branch:
1steps:2# Build Docker image with versioning3- name: 'gcr.io/cloud-builders/docker'4 args: ['build', '-t', 'gcr.io/$PROJECT_ID/website:$BUILD_ID', 5 '-t', 'gcr.io/$PROJECT_ID/website:latest', '.']6 7# Push to Google Container Registry8- name: 'gcr.io/cloud-builders/docker'9 args: ['push', 'gcr.io/$PROJECT_ID/website:$BUILD_ID']10 11# Deploy to Cloud Run with optimized settings12- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'13 entrypoint: gcloud14 args:15 - 'run'16 - 'deploy'17 - 'website'18 - '--image=gcr.io/$PROJECT_ID/website:$BUILD_ID'19 - '--region=us-central1'20 - '--platform=managed'21 - '--allow-unauthenticated' # Public website access22 - '--memory=512Mi' # Optimized memory allocation23 - '--cpu=1' # Single CPU for cost efficiency24 - '--max-instances=10' # Auto-scale up to 10 instances25 - '--min-instances=0' # Scale to zero when idle26 - '--set-env-vars=NODE_ENV=production'27 - '--update-secrets=SUPABASE_URL=supabase-url:latest'
This configuration provides:
- Automatic builds on code changes
- Versioned deployments with rollback capability
- Zero-downtime updates through Cloud Run's traffic management
- Secret management for sensitive environment variables
- Cost optimization through scale-to-zero and resource limits
Setting Up Cloud Run Deployment
Here's the complete process I used to deploy this application:
-
Project Setup: Created a new Google Cloud project and enabled the required APIs:
gcloud services enable cloudbuild.googleapis.com gcloud services enable run.googleapis.com gcloud services enable containerregistry.googleapis.com
-
Container Registry Configuration: Configured Docker authentication for pushing images:
gcloud auth configure-docker
-
Initial Deployment: The first deployment was done manually to verify everything worked:
# Build the container locally docker build -t gcr.io/PROJECT_ID/website:latest . # Push to Container Registry docker push gcr.io/PROJECT_ID/website:latest # Deploy to Cloud Run gcloud run deploy website \ --image gcr.io/PROJECT_ID/website:latest \ --region us-central1 \ --allow-unauthenticated
-
Automated Pipeline: Set up Cloud Build triggers to automatically deploy on pushes to main branch
-
Custom Domain: Mapped the custom domain through Cloud Run's domain mapping feature:
gcloud run domain-mappings create \ --service website \ --domain vivekmahendra.com \ --region us-central1
Performance and Cost Benefits
The Cloud Run deployment delivers impressive results:
- Cold starts under 1 second thanks to the optimized container
- Response times averaging 50-100ms for cached content
- Monthly costs under $5 for typical portfolio traffic
- 99.95% uptime SLA with Google's infrastructure
- Automatic scaling handling traffic spikes seamlessly
Monitoring and Observability
Cloud Run provides built-in monitoring through Google Cloud Console:
- Real-time request metrics and latency tracking
- Automatic error reporting and alerting
- Container CPU and memory utilization graphs
- Detailed request logs for debugging
The combination of React Router v7's server-side rendering and Cloud Run's edge locations ensures fast load times globally, while the scale-to-zero capability keeps hosting costs minimal for a portfolio site with variable traffic patterns.