Portfolio Architecture

Understanding the architectural decisions and structure of the portfolio project.

🏗️ Project Structure

Directory Organization

my-portfolio/
├── src/
   ├── app/                 # Next.js App Router
   ├── api/            # API routes
   ├── (routes)/       # Page routes
   ├── layout.tsx      # Root layout
   └── page.tsx        # Home page
   ├── features/           # Feature-based modules
   ├── about/         # About section
   ├── github/        # GitHub integration
   ├── projects/      # Projects showcase
   ├── skills/        # Skills visualization
   ├── three/         # Canvas background
   └── timeline/      # Experience timeline
   ├── shared/            # Shared components
   ├── components/    # Reusable UI components
   ├── hooks/         # Custom React hooks
   ├── styles/        # Global styles
   └── utils/         # Utility functions
   ├── core/              # Core utilities
   ├── config/        # Configuration
   └── utils/         # Core utilities
   ├── lib/               # Third-party integrations
   └── types/             # TypeScript definitions
├── public/                # Static assets
├── tests/                 # Test files
   ├── cypress/          # E2E tests
   └── __tests__/        # Unit tests
└── docs/                  # Documentation

Feature-Based Architecture

Each feature is self-contained with its own:

  • Components
  • Types
  • Services
  • Hooks
  • Constants

Example: features/projects/

projects/
├── components/
   ├── ProjectCard.tsx
   ├── ProjectsGrid.tsx
   └── index.tsx
├── data/
   └── constants.ts
├── hooks/
   └── useProjects.ts
├── services/
   └── projectService.ts
└── types/
    └── index.ts

🎨 Component Patterns

Server Components (Default)

Used for static content and data fetching:

// Server Component - no "use client"
export default async function ProjectsPage() {
  const projects = await getProjects();
  
  return (
    <div>
      <h1>My Projects</h1>
      <ProjectsGrid projects={projects} />
    </div>
  );
}

Client Components

Used for interactivity and browser APIs:

"use client";

import { useState } from 'react';

export function ProjectCard({ project }: ProjectCardProps) {
  const [isHovered, setIsHovered] = useState(false);
  
  return (
    <div 
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      {/* Interactive content */}
    </div>
  );
}

Compound Components

For complex UI with shared state:

// Parent component
export function Timeline({ children }: TimelineProps) {
  return (
    <div className="timeline">
      {children}
    </div>
  );
}

// Child components
Timeline.Item = TimelineItem;
Timeline.Dot = TimelineDot;
Timeline.Content = TimelineContent;

// Usage
<Timeline>
  <Timeline.Item>
    <Timeline.Dot />
    <Timeline.Content>...</Timeline.Content>
  </Timeline.Item>
</Timeline>

🔄 Data Flow

GitHub Integration

sequenceDiagram
    participant Client
    participant API Route
    participant GitHub API
    participant Cache

    Client->>API Route: Request stats
    API Route->>Cache: Check cache
    alt Cache hit
        Cache-->>API Route: Return cached data
    else Cache miss
        API Route->>GitHub API: Fetch data
        GitHub API-->>API Route: Return data
        API Route->>Cache: Store in cache
    end
    API Route-->>Client: Return response

State Management

Using React hooks and context:

// Theme Provider
const ThemeContext = createContext<ThemeContextType>(null!);

export function ThemeProvider({ children }: Props) {
  const [theme, setTheme] = useState<Theme>('dark');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Custom hook
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

🎯 Design Patterns

cn() Utility Pattern

For className composition:

import { cn } from "@/core/utils/utils";

export function Button({ className, variant, ...props }: ButtonProps) {
  return (
    <button
      className={cn(
        "base-styles",
        variant === "primary" && "primary-styles",
        variant === "secondary" && "secondary-styles",
        className // Allow overrides
      )}
      {...props}
    />
  );
}

Class Variance Authority (CVA)

For component variants:

import { cva, type VariantProps } from "class-variance-authority";

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        destructive: "bg-destructive text-destructive-foreground",
        outline: "border border-input bg-background",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 px-3",
        lg: "h-11 px-8",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

interface ButtonProps 
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

export function Button({ variant, size, className, ...props }: ButtonProps) {
  return (
    <button
      className={cn(buttonVariants({ variant, size }), className)}
      {...props}
    />
  );
}

Custom Hooks Pattern

Encapsulating reusable logic:

// useProjects.ts
export function useProjects() {
  const [projects, setProjects] = useState<Project[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    async function fetchProjects() {
      try {
        const data = await projectService.getAll();
        setProjects(data);
      } catch (err) {
        setError(err as Error);
      } finally {
        setLoading(false);
      }
    }
    fetchProjects();
  }, []);

  return { projects, loading, error };
}

// Usage
function ProjectsPage() {
  const { projects, loading, error } = useProjects();
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  
  return <ProjectsGrid projects={projects} />;
}

🔐 Type Safety

TypeScript Configuration

Strict mode enabled:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}

Type Definitions

Centralized type definitions:

// types/index.ts
export interface Project {
  id: string;
  title: string;
  description: string;
  technologies: string[];
  githubUrl?: string;
  liveUrl?: string;
  imageUrl?: string;
  featured: boolean;
}

export interface TimelineEvent {
  id: string;
  title: string;
  organization: string;
  period: string;
  description: string;
  type: 'education' | 'work' | 'certification';
}

export interface GitHubStats {
  followers: number;
  following: number;
  publicRepos: number;
  totalStars: number;
  contributions: number;
}

Zod Validation

Runtime type validation for API routes:

import { z } from 'zod';

const contactSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  message: z.string().min(10, 'Message must be at least 10 characters'),
});

export type ContactFormData = z.infer<typeof contactSchema>;

// API route
export async function POST(request: Request) {
  const body = await request.json();
  const result = contactSchema.safeParse(body);
  
  if (!result.success) {
    return Response.json(
      { error: result.error.flatten() },
      { status: 400 }
    );
  }
  
  // Use result.data (fully typed)
  await sendContactEmail(result.data);
  return Response.json({ success: true });
}

⚡ Performance Optimizations

Code Splitting

Automatic route-based splitting:

// Dynamic imports for heavy components
const ThreeBackground = dynamic(
  () => import('@/features/three/GlobalBackground3D'),
  { ssr: false }
);

Image Optimization

Using Next.js Image component:

import Image from 'next/image';

<Image
  src="/project-screenshot.png"
  alt="Project screenshot"
  width={800}
  height={600}
  priority={false}
  loading="lazy"
/>

Caching Strategy

API route caching with Redis:

import { Redis } from '@vercel/kv';

export async function GET() {
  const cached = await Redis.get('github-stats');
  
  if (cached) {
    return Response.json(cached);
  }
  
  const stats = await fetchGitHubStats();
  await Redis.set('github-stats', stats, { ex: 3600 }); // 1 hour
  
  return Response.json(stats);
}

🧪 Testing Architecture

Unit Tests (Jest)

Component and utility testing:

import { render, screen } from '@testing-library/react';
import { ProjectCard } from './ProjectCard';

describe('ProjectCard', () => {
  it('renders project title', () => {
    const project = { 
      title: 'Test Project',
      description: 'Test description',
      technologies: ['React', 'TypeScript']
    };
    
    render(<ProjectCard project={project} />);
    expect(screen.getByText('Test Project')).toBeInTheDocument();
  });
});

E2E Tests (Cypress)

Full user flow testing:

describe('Portfolio Navigation', () => {
  it('navigates to projects section', () => {
    cy.visit('/');
    cy.get('[data-testid="projects-link"]').click();
    cy.url().should('include', '/projects');
    cy.get('h1').should('contain', 'My Projects');
  });
});

📚 Learn More

On this page