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/ # DocumentationFeature-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 responseState 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');
});
});