Table of Contents
1. Why Build Portfolio Projects
In a competitive job market, a well-crafted portfolio is your strongest weapon. Recruiters spend an average of 6 seconds scanning a resume — but they'll spend minutes exploring a live project that demonstrates your skills in action.
Portfolio projects serve three critical purposes:
- Stand out from the crowd: Thousands of developers claim to know React on their resumes. A deployed, functional project proves it.
- Learn by doing: Tutorials teach syntax, but projects teach problem-solving. You'll encounter real bugs, edge cases, and architectural decisions.
- Build a conversation starter: During interviews, discussing your projects gives you control of the narrative. You can walk through your decisions, trade-offs, and what you learned.
The key is building projects that are unique enough to stand out but familiar enough that recruiters understand the problem you're solving. A clone of Netflix is less impressive than a novel tool that solves a real problem — even if it's simpler.
2. Beginner Projects
These projects focus on fundamental React concepts: components, state management, props, and API integration. They're ideal for developers who've completed a React course and want to apply their knowledge.
Todo App with Full CRUD
Go beyond a basic list. Build a todo app with create, read, update, delete, filtering, priority levels, and localStorage persistence.
// TodoItem.jsx - Reusable component with edit/delete
import { useState } from 'react';
function TodoItem({ todo, onToggle, onDelete, onEdit }) {
const [isEditing, setIsEditing] = useState(false);
const [editText, setEditText] = useState(todo.text);
const handleSave = () => {
if (editText.trim()) {
onEdit(todo.id, editText);
setIsEditing(false);
}
};
return (
<li className={`todo-item ${todo.completed ? 'completed' : ''}`}>
{isEditing ? (
<input
type="text"
value={editText}
onChange={(e) => setEditText(e.target.value)}
onBlur={handleSave}
onKeyDown={(e) => e.key === 'Enter' && handleSave()}
autoFocus
/>
) : (
<span onDoubleClick={() => setIsEditing(true)}>
{todo.text}
</span>
)}
<div className="todo-actions">
<button onClick={() => onToggle(todo.id)}>
{todo.completed ? '↩' : '✓'}
</button>
<button onClick={() => onDelete(todo.id)}>✕</button>
</div>
</li>
);
}
Weather App
Fetch real weather data from an API (OpenWeatherMap), display current conditions, forecasts, and handle loading/error states gracefully.
// useWeather.js - Custom hook for weather data
import { useState, useEffect } from 'react';
export function useWeather(city) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!city) return;
const controller = new AbortController();
async function fetchWeather() {
setLoading(true);
try {
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${import.meta.env.VITE_API_KEY}&units=metric`,
{ signal: controller.signal }
);
if (!res.ok) throw new Error('City not found');
setData(await res.json());
} catch (err) {
if (err.name !== 'AbortError') setError(err.message);
} finally {
setLoading(false);
}
}
fetchWeather();
return () => controller.abort();
}, [city]);
return { data, loading, error };
}
Quiz App
Build an interactive quiz with timed questions, score tracking, multiple categories, and a results screen. Use the Open Trivia DB API for questions.
// QuizApp.jsx - Core quiz state with useReducer
import { useReducer } from 'react';
const quizReducer = (state, action) => {
switch (action.type) {
case 'START_QUIZ':
return { ...state, questions: action.questions, currentQ: 0, score: 0, answered: false };
case 'ANSWER':
const correct = action.answer === state.questions[state.currentQ].correct_answer;
return { ...state, score: correct ? state.score + 1 : state.score, answered: true, selectedAnswer: action.answer };
case 'NEXT_QUESTION':
return { ...state, currentQ: state.currentQ + 1, answered: false, selectedAnswer: null };
case 'RESET':
return { questions: [], currentQ: 0, score: 0, answered: false, selectedAnswer: null };
default:
return state;
}
};
3. Intermediate Projects
These projects introduce more complex patterns: state management libraries, routing, drag-and-drop, and API design.
E-commerce Shopping Cart
Build a full shopping experience with product listing, filtering, cart management, checkout flow, and order history. Use Zustand or Context API for state.
- Product catalog with search, filter by category/price
- Shopping cart with quantity management and total calculation
- Checkout form with validation
- Order history with localStorage persistence
Blog with CMS
Create a markdown-powered blog with a simple admin dashboard. Use MDX for rich content and a lightweight headless CMS or JSON-based storage.
// BlogPost.jsx - Dynamic MDX rendering
import { MDXProvider } from '@mdx-js/react';
import CodeBlock from './CodeBlock';
import Callout from './Callout';
const components = {
pre: CodeBlock,
Callout,
h2: (props) => <h2 className="blog-heading" {...props} />,
};
export default function BlogPost({ content, frontmatter }) {
return (
<article className="blog-post">
<header>
<h1>{frontmatter.title}</h1>
<time>{frontmatter.date}</time>
</header>
<MDXProvider components={components}>
{content}
</MDXProvider>
</article>
);
}
Task Manager with Drag-and-Drop
A Kanban-style board (like Trello) using @dnd-kit or react-beautiful-dnd for drag-and-drop between columns.
// KanbanBoard.jsx - Drag and drop with @dnd-kit
import { DndContext, closestCorners } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
function KanbanBoard({ columns, onMoveTask }) {
const handleDragEnd = (event) => {
const { active, over } = event;
if (!over || active.id === over.id) return;
onMoveTask(active.id, over.id);
};
return (
<DndContext collisionDetection={closestCorners} onDragEnd={handleDragEnd}>
<div className="kanban-board">
{columns.map((col) => (
<KanbanColumn key={col.id} column={col} />
))}
</div>
</DndContext>
);
}
react-beautiful-dnd has known issues on mobile — consider @dnd-kit as a more modern alternative.
4. Advanced Projects
These projects demonstrate mastery of complex React patterns, real-time data, and system design.
Real-time Chat Application
Build a messaging app with WebSocket connections, typing indicators, message history, and online status. Use Socket.io with a Node.js backend or Firebase Realtime Database.
- Real-time message delivery with WebSockets
- Typing indicators and online/offline status
- Message read receipts
- Image/file sharing
- Chat rooms and direct messages
Social Media Dashboard
An analytics dashboard with real-time charts, data aggregation, and API integrations. Pull data from multiple sources and visualize trends.
- Interactive charts with Recharts or D3.js
- Date range filtering and data export
- Responsive grid layout with widget customization
- WebSocket for live data updates
AI-powered Application
Integrate OpenAI or a local LLM into a React app. Ideas include a code review tool, writing assistant, or image analyzer.
// useAI.js - Custom hook for AI streaming responses
import { useState, useCallback } from 'react';
export function useAI() {
const [response, setResponse] = useState('');
const [loading, setLoading] = useState(false);
const generate = useCallback(async (prompt) => {
setLoading(true);
setResponse('');
try {
const res = await fetch('/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
setResponse((prev) => prev + decoder.decode(value));
}
} finally {
setLoading(false);
}
}, []);
return { response, loading, generate };
}
5. How to Choose the Right Project
The best project is one you'll actually finish. Here's a framework for choosing:
Assess Your Skill Level
Be honest about where you are. If you just learned React last week, don't start with a real-time chat app. Start with a weather app and work your way up.
Consider Your Time
- 1-2 weeks: Todo app, calculator, weather app
- 3-4 weeks: E-commerce cart, blog with CMS, task manager
- 1-2 months: Chat app, social dashboard, full-stack app
Follow Your Interest
Passion projects are easier to finish and more impressive to discuss. Love music? Build a Spotify clone. Into fitness? Build a workout tracker. Interested in finance? Build a budget dashboard.
6. Showcasing Projects on Your Portfolio
How you present a project matters as much as the project itself.
Essential Elements for Each Project
- Screenshot or video: A hero image showing the app in action
- Live demo link: Deployed version — recruiters want to click and explore
- GitHub repository: Clean code with a comprehensive README
- Technology stack: List the frameworks, libraries, and tools used
- Key features: Bullet points of what makes this project notable
Writing a Great README
# Weather Dashboard
A responsive weather application built with React and OpenWeatherMap API.
## Features
- Current weather with 5-day forecast
- Search by city name or use geolocation
- Responsive design (mobile-first)
- Unit conversion (°C / °F)
- Loading skeletons and error states
## Tech Stack
- React 18 + Vite
- Tailwind CSS
- OpenWeatherMap API
- React Testing Library + Vitest
## Live Demo
🔗 [weather-demo.vercel.app](https://weather-demo.vercel.app)
## Getting Started
npm install
npm run dev
7. Adding Tests to Your Projects
Tests show that you care about code quality and reliability. Even basic test coverage sets you apart from other applicants.
Unit Testing with Vitest
// calculator.test.js - Unit testing calculator logic
import { describe, it, expect } from 'vitest';
import { calculate } from './calculator';
describe('Calculator', () => {
it('adds two numbers correctly', () => {
expect(calculate(2, '+', 3)).toBe(5);
});
it('divides by zero returns error', () => {
expect(calculate(10, '/', 0)).toBe('Error');
});
it('handles decimal precision', () => {
expect(calculate(0.1, '+', 0.2)).toBeCloseTo(0.3);
});
it('chains operations', () => {
let result = calculate(5, '+', 3);
result = calculate(result, '*', 2);
expect(result).toBe(16);
});
});
Integration Testing with React Testing Library
// TodoApp.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import TodoApp from './TodoApp';
describe('TodoApp', () => {
it('adds a new todo', () => {
render(<TodoApp />);
fireEvent.change(screen.getByPlaceholderText(/add a task/i), {
target: { value: 'Write tests' },
});
fireEvent.click(screen.getByText(/add/i));
expect(screen.getByText('Write tests')).toBeInTheDocument();
});
it('toggles todo completion', () => {
render(<TodoApp />);
const todo = screen.getByText('Write tests');
fireEvent.click(todo);
expect(todo).toHaveClass('completed');
});
});
8. Deployment Checklist
Before deploying, run through this checklist to ensure your project is production-ready:
Environment Variables
- Never commit
.envfiles — add to.gitignore - Use environment-specific prefixes:
VITE_API_KEY - Document required env vars in your README
- Set env vars in your hosting platform (Vercel, Netlify, etc.)
Build Optimization
// vite.config.js - Production optimizations
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
charts: ['recharts'],
},
},
},
chunkSizeWarningLimit: 500,
},
});
Performance
- Run Lighthouse audit — aim for 90+ on all metrics
- Check bundle size with
npm run build -- --analyze - Implement lazy loading for routes and heavy components
- Optimize images (WebP format, proper sizing)
- Enable compression on your hosting platform
9. Common Mistakes to Avoid
After reviewing hundreds of developer portfolios, here are the most common mistakes:
Too Many Projects, No Depth
Five half-built projects are worse than two polished ones. Each project should demonstrate a specific skill set and be fully functional.
No Documentation
A project without a README is a red flag. If you can't document your own code, how will you document shared code at work?
Broken Links and Dead Demos
Always check your live demo links before sharing your portfolio. A broken link is an instant turn-off. Deploy on reliable platforms and test after every update.
Not Explaining Your Decisions
In your README or project description, explain why you made certain choices. "I chose Zustand over Redux because..." shows critical thinking.
Ignoring Accessibility
Add proper ARIA labels, keyboard navigation, and semantic HTML. This is a bonus that shows professional-level attention to detail.